diff --git a/Cargo.lock b/Cargo.lock index e0c1750e6d3..8edfbfc5495 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1948,6 +1948,7 @@ dependencies = [ name = "iroha_ffi_derive" version = "2.0.0-pre-rc.5" dependencies = [ + "derive_more", "getset", "iroha_ffi", "proc-macro-error", diff --git a/cli/src/torii/tests.rs b/cli/src/torii/tests.rs index 546c89f7892..7b555ec1d27 100644 --- a/cli/src/torii/tests.rs +++ b/cli/src/torii/tests.rs @@ -747,14 +747,6 @@ async fn blocks_stream() { assert_eq!(block.header().height, BLOCK_COUNT as u64 + 1); } -/// Returns the a map of a form `domain_name -> domain`, for initial domains. -fn domains( - configuration: &crate::config::Configuration, -) -> eyre::Result> { - let key = configuration.genesis.account_public_key.clone(); - Ok([Domain::from(GenesisDomain::new(key))].into_iter()) -} - #[test] fn hash_should_be_the_same() { prepare_test_for_nextest!(); @@ -786,7 +778,7 @@ fn hash_should_be_the_same() { AcceptedTransaction::from_transaction(signed_tx, &tx_limits).expect("Failed to accept."); let accepted_tx_hash = accepted_tx.hash(); let wsv = Arc::new(WorldStateView::new(World::with( - domains(&config).unwrap(), + crate::domains(&config), BTreeSet::new(), ))); let valid_tx_hash = TransactionValidator::new( diff --git a/crypto/Cargo.toml b/crypto/Cargo.toml index 9a54ab7948f..3bb86c1bb21 100644 --- a/crypto/Cargo.toml +++ b/crypto/Cargo.toml @@ -13,8 +13,8 @@ default = ["std"] std = ["ursa"] # Force static linking vendored = ["openssl-sys"] -# Expose FFI API to facilitate dynamic linking -ffi_api = ["iroha_ffi"] +# Generate extern functions callable via FFI +ffi = ["iroha_ffi"] [dependencies] iroha_primitives = { path = "../primitives", version = "=2.0.0-pre-rc.5", default-features = false } diff --git a/crypto/src/hash.rs b/crypto/src/hash.rs index ab391112fab..ad450c685f4 100644 --- a/crypto/src/hash.rs +++ b/crypto/src/hash.rs @@ -167,13 +167,13 @@ impl IntoSchema for HashOf { format!("{}::HashOf<{}>", module_path!(), T::type_name()) } fn schema(map: &mut MetaMap) { - Hash::schema(map); - map.entry(Self::type_name()).or_insert_with(|| { Metadata::Tuple(UnnamedFieldsMeta { types: vec![Hash::type_name()], }) }); + + Hash::schema(map); } } diff --git a/crypto/src/lib.rs b/crypto/src/lib.rs index 2d6acd31afb..288d228cc4b 100644 --- a/crypto/src/lib.rs +++ b/crypto/src/lib.rs @@ -20,6 +20,8 @@ pub use base64; use derive_more::{DebugCustom, Display}; use getset::Getters; pub use hash::*; +#[cfg(feature = "ffi")] +use iroha_ffi::{ffi_export, IntoFfi, TryFromFfi}; use iroha_primitives::conststr::ConstString; use iroha_schema::IntoSchema; pub use merkle::MerkleTree; @@ -62,7 +64,9 @@ pub struct NoSuchAlgorithm; impl std::error::Error for NoSuchAlgorithm {} /// Algorithm for hashing +#[cfg_attr(feature = "ffi", derive(IntoFfi, TryFromFfi))] #[derive(Debug, Display, Clone, Copy, PartialEq, Eq)] +#[repr(u8)] pub enum Algorithm { /// Ed25519 #[display(fmt = "{}", "ED_25519")] @@ -161,6 +165,7 @@ impl KeyGenConfiguration { } /// Pair of Public and Private keys. +#[cfg_attr(feature = "ffi", derive(IntoFfi, TryFromFfi))] #[derive(Debug, Clone, PartialEq, Eq, Getters, Serialize)] #[getset(get = "pub")] pub struct KeyPair { @@ -359,21 +364,26 @@ impl From for KeyParseError { impl std::error::Error for KeyParseError {} /// Public Key used in signatures. -#[derive(DebugCustom, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Getters, Encode, IntoSchema)] -#[getset(get = "pub")] +#[derive(DebugCustom, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Encode, IntoSchema)] +#[cfg_attr(feature = "ffi", derive(IntoFfi, TryFromFfi))] #[debug( fmt = "{{ digest: {digest_function}, payload: {} }}", "hex::encode_upper(payload.as_slice())" )] pub struct PublicKey { /// Digest function - #[getset(skip)] digest_function: ConstString, /// payload of key payload: Vec, } +#[cfg_attr(feature = "ffi", ffi_export)] impl PublicKey { + /// Key payload + pub fn payload(&self) -> &[u8] { + &self.payload + } + /// Digest function #[allow(clippy::expect_used)] pub fn digest_function(&self) -> Algorithm { @@ -485,19 +495,33 @@ impl Decode for PublicKey { } /// Private Key used in signatures. -#[derive(DebugCustom, Display, Clone, PartialEq, Eq, Getters, Serialize)] -#[getset(get = "pub")] +#[derive(DebugCustom, Display, Clone, PartialEq, Eq, Serialize)] +#[cfg_attr(feature = "ffi", derive(IntoFfi, TryFromFfi))] #[debug(fmt = "{{ digest: {digest_function}, payload: {:X?}}}", payload)] #[display(fmt = "{}", "hex::encode(payload)")] +#[allow(clippy::multiple_inherent_impl)] pub struct PrivateKey { /// Digest function - #[getset(skip)] digest_function: ConstString, /// key payload. WARNING! Do not use `"string".as_bytes()` to obtain the key. #[serde(with = "hex::serde")] payload: Vec, } +#[cfg_attr(feature = "ffi", ffi_export)] +impl PrivateKey { + /// Key payload + pub fn payload(&self) -> &[u8] { + &self.payload + } + + /// Digest function + #[allow(clippy::expect_used)] + pub fn digest_function(&self) -> Algorithm { + self.digest_function.parse().expect("Valid") + } +} + impl PrivateKey { /// Construct `PrivateKey` from hex encoded string /// @@ -523,12 +547,6 @@ impl PrivateKey { payload: hex::decode(payload)?, }) } - - /// Digest function - #[allow(clippy::expect_used)] - pub fn digest_function(&self) -> Algorithm { - self.digest_function.parse().expect("Valid") - } } impl<'de> Deserialize<'de> for PrivateKey { @@ -556,6 +574,20 @@ impl<'de> Deserialize<'de> for PrivateKey { } } +#[cfg(feature = "ffi")] +mod ffi { + use iroha_ffi::{gen_ffi_impl, handles}; + + use super::{KeyPair, PrivateKey, PublicKey}; + + handles! {0, KeyPair, PublicKey, PrivateKey} + + gen_ffi_impl! { Clone: KeyPair, PublicKey, PrivateKey} + gen_ffi_impl! { Eq: KeyPair, PublicKey, PrivateKey} + gen_ffi_impl! { Ord: PublicKey } + gen_ffi_impl! { Drop: KeyPair, PublicKey, PrivateKey} +} + /// The prelude re-exports most commonly used traits, structs and macros from this crate. pub mod prelude { pub use super::{Algorithm, Hash, KeyPair, PrivateKey, PublicKey, Signature}; diff --git a/crypto/src/merkle.rs b/crypto/src/merkle.rs index b0f5e5aa97a..798dece162f 100644 --- a/crypto/src/merkle.rs +++ b/crypto/src/merkle.rs @@ -125,16 +125,15 @@ impl IntoSchema for MerkleTree { format!("{}::MerkleTree<{}>", module_path!(), T::type_name()) } fn schema(map: &mut MetaMap) { + // NOTE: Leaf nodes in the order of insertion map.entry(Self::type_name()).or_insert_with(|| { - // BFS ordered list of leaf nodes Metadata::Vec(VecMeta { ty: HashOf::::type_name(), - sorted: true, + sorted: false, }) }); - if !map.contains_key(&HashOf::::type_name()) { - HashOf::::schema(map); - } + + HashOf::::schema(map); } } diff --git a/crypto/src/signature.rs b/crypto/src/signature.rs index a9c2a9d47a0..281dcb0db06 100644 --- a/crypto/src/signature.rs +++ b/crypto/src/signature.rs @@ -102,7 +102,7 @@ impl Signature { #[cfg(feature = "std")] pub fn verify(&self, payload: &[u8]) -> Result<(), Error> { let algorithm: Algorithm = self.public_key.digest_function(); - let public_key = UrsaPublicKey(self.public_key.payload().clone()); + let public_key = UrsaPublicKey(self.public_key.payload().to_owned()); match algorithm { Algorithm::Ed25519 => Ed25519Sha512::new().verify(payload, self.payload(), &public_key), @@ -186,13 +186,13 @@ impl IntoSchema for SignatureOf { format!("{}::SignatureOf<{}>", module_path!(), T::type_name()) } fn schema(map: &mut MetaMap) { - Signature::schema(map); - map.entry(Self::type_name()).or_insert_with(|| { Metadata::Tuple(UnnamedFieldsMeta { types: vec![Signature::type_name()], }) }); + + Signature::schema(map); } } diff --git a/data_model/Cargo.toml b/data_model/Cargo.toml index a78b6a2f0fd..4d861f376d4 100644 --- a/data_model/Cargo.toml +++ b/data_model/Cargo.toml @@ -22,8 +22,8 @@ default = ["std"] # Disabled for WASM interoperability, to reduce the binary size. # Please refer to https://docs.rust-embedded.org/book/intro/no-std.html std = ["iroha_macro/std", "iroha_version/std", "iroha_version/warp", "iroha_crypto/std", "iroha_primitives/std", "thiserror", "strum/std", "dashmap", "tokio"] -# Expose FFI API to facilitate dynamic linking -ffi_api = ["iroha_ffi", "iroha_crypto/ffi_api"] +# Generate extern functions callable via FFI +ffi = ["iroha_crypto/ffi", "iroha_ffi"] # Expose API for mutating structures (Internal use only) mutable_api = [] diff --git a/data_model/src/account.rs b/data_model/src/account.rs index 93d1022b020..0f4ff75391a 100644 --- a/data_model/src/account.rs +++ b/data_model/src/account.rs @@ -13,8 +13,8 @@ use std::collections::{btree_map, btree_set}; use derive_more::Display; use getset::{Getters, MutGetters, Setters}; -#[cfg(feature = "ffi_api")] -use iroha_ffi::ffi_bindgen; +#[cfg(feature = "ffi")] +use iroha_ffi::{ffi_export, IntoFfi, TryFromFfi}; use iroha_schema::IntoSchema; use parity_scale_codec::{Decode, Encode}; use serde::{Deserialize, Serialize}; @@ -82,14 +82,15 @@ impl From for Account { Clone, PartialEq, Eq, + PartialOrd, + Ord, Decode, Encode, Deserialize, Serialize, IntoSchema, - PartialOrd, - Ord, )] +#[cfg_attr(feature = "ffi", derive(IntoFfi, TryFromFfi))] pub struct SignatureCheckCondition(pub EvaluatesTo); impl SignatureCheckCondition { @@ -128,6 +129,7 @@ impl Default for SignatureCheckCondition { #[derive( Debug, Display, Clone, PartialEq, Eq, Decode, Encode, Deserialize, Serialize, IntoSchema, )] +#[cfg_attr(feature = "ffi", derive(IntoFfi, TryFromFfi))] #[display(fmt = "[{id}]")] pub struct NewAccount { /// Identification @@ -190,12 +192,12 @@ impl NewAccount { } /// Identification - pub fn id(&self) -> &::Id { + pub(crate) fn id(&self) -> &::Id { &self.id } } -#[cfg_attr(feature = "ffi_api", ffi_bindgen)] +#[cfg_attr(feature = "ffi", ffi_export)] impl NewAccount { /// Add [`Metadata`] to the account replacing previously defined #[must_use] @@ -221,9 +223,9 @@ impl NewAccount { Serialize, IntoSchema, )] -#[allow(clippy::multiple_inherent_impl)] -#[cfg_attr(feature = "ffi_api", ffi_bindgen)] +#[cfg_attr(feature = "ffi", derive(IntoFfi, TryFromFfi))] #[display(fmt = "({id})")] // TODO: Add more? +#[allow(clippy::multiple_inherent_impl)] pub struct Account { /// An Identification of the [`Account`]. id: ::Id, @@ -234,7 +236,8 @@ pub struct Account { /// Permissions tokens of this account permission_tokens: Permissions, /// Condition which checks if the account has the right signatures. - #[cfg_attr(feature = "mutable_api", getset(get = "pub", set = "pub"))] + #[getset(get = "pub")] + #[cfg_attr(feature = "mutable_api", getset(set = "pub"))] signature_check_condition: SignatureCheckCondition, /// Metadata of this account as a key-value store. #[cfg_attr(feature = "mutable_api", getset(get_mut = "pub"))] @@ -275,7 +278,7 @@ impl Ord for Account { } } -#[cfg_attr(feature = "ffi_api", ffi_bindgen)] +#[cfg_attr(feature = "ffi", ffi_export)] impl Account { /// Construct builder for [`Account`] identifiable by [`Id`] containing the given signatories. #[must_use] @@ -434,6 +437,7 @@ impl FromIterator for crate::Value { Serialize, IntoSchema, )] +#[cfg_attr(feature = "ffi", derive(IntoFfi, TryFromFfi))] #[display(fmt = "{name}@{domain_id}")] pub struct Id { /// [`Account`]'s name. diff --git a/data_model/src/asset.rs b/data_model/src/asset.rs index 8fe7ba6850e..45a84f07def 100644 --- a/data_model/src/asset.rs +++ b/data_model/src/asset.rs @@ -2,13 +2,15 @@ //! instructions implementations. #[cfg(not(feature = "std"))] -use alloc::{collections::btree_map, format, string::String, vec::Vec}; +use alloc::{boxed::Box, collections::btree_map, format, string::String, vec::Vec}; use core::{cmp::Ordering, str::FromStr}; #[cfg(feature = "std")] use std::collections::btree_map; use derive_more::Display; -use getset::{Getters, MutGetters, Setters}; +use getset::{Getters, MutGetters}; +#[cfg(feature = "ffi")] +use iroha_ffi::{ffi_export, IntoFfi, TryFromFfi}; use iroha_macro::FromVariant; use iroha_primitives::{fixed, fixed::Fixed}; use iroha_schema::IntoSchema; @@ -58,9 +60,10 @@ impl std::error::Error for MintabilityError {} Serialize, IntoSchema, )] +#[cfg_attr(feature = "ffi", derive(IntoFfi, TryFromFfi))] +#[cfg_attr(feature = "ffi", ffi_export)] #[getset(get = "pub")] #[allow(clippy::multiple_inherent_impl)] -#[cfg_attr(feature = "ffi_api", iroha_ffi::ffi_bindgen)] pub struct AssetDefinitionEntry { /// Asset definition. #[cfg_attr(feature = "mutable_api", getset(get_mut = "pub"))] @@ -83,7 +86,7 @@ impl Ord for AssetDefinitionEntry { } } -#[cfg_attr(feature = "ffi_api", iroha_ffi::ffi_bindgen)] +#[cfg_attr(feature = "ffi", ffi_export)] impl AssetDefinitionEntry { /// Constructor. pub const fn new( @@ -117,15 +120,14 @@ impl AssetDefinitionEntry { Eq, Getters, MutGetters, - Setters, Decode, Encode, Deserialize, Serialize, IntoSchema, )] +#[cfg_attr(feature = "ffi", derive(IntoFfi, TryFromFfi))] #[allow(clippy::multiple_inherent_impl)] -#[cfg_attr(feature = "ffi_api", iroha_ffi::ffi_bindgen)] #[display(fmt = "{id} {value_type}{mintable}")] pub struct AssetDefinition { /// An Identification of the [`AssetDefinition`]. @@ -179,6 +181,8 @@ impl Ord for AssetDefinition { Serialize, IntoSchema, )] +#[cfg_attr(feature = "ffi", derive(IntoFfi, TryFromFfi))] +#[repr(u8)] pub enum Mintable { /// Regular asset with elastic supply. Can be minted and burned. #[display(fmt = "+")] @@ -207,8 +211,9 @@ pub enum Mintable { Serialize, IntoSchema, )] +#[cfg_attr(feature = "ffi", derive(IntoFfi, TryFromFfi))] +#[cfg_attr(feature = "ffi", ffi_export)] #[getset(get = "pub")] -#[cfg_attr(feature = "ffi_api", iroha_ffi::ffi_bindgen)] #[display(fmt = "{id}: {value}")] pub struct Asset { /// Component Identification. @@ -235,6 +240,8 @@ pub struct Asset { IntoSchema, EnumString, )] +#[cfg_attr(feature = "ffi", derive(IntoFfi, TryFromFfi))] +#[repr(u8)] pub enum AssetValueType { /// Asset's Quantity. #[display(fmt = "q")] @@ -264,6 +271,8 @@ pub enum AssetValueType { FromVariant, IntoSchema, )] +#[cfg_attr(feature = "ffi", derive(IntoFfi, TryFromFfi))] +#[repr(u8)] pub enum AssetValue { /// Asset's Quantity. #[display(fmt = "{_0}q")] @@ -372,6 +381,7 @@ impl_try_as_for_asset_value! { Serialize, IntoSchema, )] +#[cfg_attr(feature = "ffi", derive(IntoFfi, TryFromFfi))] #[display(fmt = "{name}#{domain_id}")] pub struct DefinitionId { /// Asset's name. @@ -396,6 +406,7 @@ pub struct DefinitionId { Serialize, IntoSchema, )] +#[cfg_attr(feature = "ffi", derive(IntoFfi, TryFromFfi))] #[display(fmt = "{definition_id}@{account_id}")] // TODO: change this? pub struct Id { /// Entity Identification. @@ -409,6 +420,7 @@ pub struct Id { #[derive( Debug, Display, Clone, PartialEq, Eq, Decode, Encode, Deserialize, Serialize, IntoSchema, )] +#[cfg_attr(feature = "ffi", derive(IntoFfi, TryFromFfi))] #[display(fmt = "{id} {mintable}{value_type}")] pub struct NewAssetDefinition { id: ::Id, @@ -466,12 +478,12 @@ impl NewAssetDefinition { /// Identification #[inline] - pub fn id(&self) -> &::Id { + pub(crate) fn id(&self) -> &::Id { &self.id } } -#[cfg_attr(feature = "ffi_api", iroha_ffi::ffi_bindgen)] +#[cfg_attr(feature = "ffi", ffi_export)] impl NewAssetDefinition { /// Set mintability to [`Mintable::Once`] #[inline] @@ -490,7 +502,7 @@ impl NewAssetDefinition { } } -#[cfg_attr(feature = "ffi_api", iroha_ffi::ffi_bindgen)] +#[cfg_attr(feature = "ffi", ffi_export)] impl AssetDefinition { /// Construct builder for [`AssetDefinition`] identifiable by [`Id`]. #[must_use] @@ -538,7 +550,7 @@ impl AssetDefinition { } } -#[cfg_attr(feature = "ffi_api", iroha_ffi::ffi_bindgen)] +#[cfg_attr(feature = "ffi", ffi_export)] impl Asset { /// Constructor pub fn new( diff --git a/data_model/src/block_value.rs b/data_model/src/block_value.rs index 0a9784fcf83..11f281f05aa 100644 --- a/data_model/src/block_value.rs +++ b/data_model/src/block_value.rs @@ -40,7 +40,7 @@ pub struct BlockHeaderValue { impl PartialOrd for BlockHeaderValue { fn partial_cmp(&self, other: &Self) -> Option { - Some(self.timestamp.cmp(&other.timestamp)) + Some(self.cmp(other)) } } @@ -68,7 +68,7 @@ pub struct BlockValue { impl PartialOrd for BlockValue { fn partial_cmp(&self, other: &Self) -> Option { - Some(self.header.cmp(&other.header)) + Some(self.cmp(other)) } } diff --git a/data_model/src/domain.rs b/data_model/src/domain.rs index 091f0cdd423..86711588630 100644 --- a/data_model/src/domain.rs +++ b/data_model/src/domain.rs @@ -12,8 +12,8 @@ use core::{cmp::Ordering, str::FromStr}; use derive_more::{Display, FromStr}; use getset::{Getters, MutGetters}; use iroha_crypto::PublicKey; -#[cfg(feature = "ffi_api")] -use iroha_ffi::ffi_bindgen; +#[cfg(feature = "ffi")] +use iroha_ffi::{ffi_export, IntoFfi, TryFromFfi}; use iroha_primitives::conststr::ConstString; use iroha_schema::IntoSchema; use parity_scale_codec::{Decode, Encode, Input}; @@ -74,6 +74,7 @@ impl From for Domain { #[derive( Debug, Display, Clone, PartialEq, Eq, Decode, Encode, Deserialize, Serialize, IntoSchema, )] +#[cfg_attr(feature = "ffi", derive(IntoFfi, TryFromFfi))] #[allow(clippy::multiple_inherent_impl)] #[display(fmt = "[{id}]")] pub struct NewDomain { @@ -135,12 +136,12 @@ impl NewDomain { } /// Identification - pub fn id(&self) -> &::Id { + pub(crate) fn id(&self) -> &::Id { &self.id } } -#[cfg_attr(feature = "ffi_api", ffi_bindgen)] +#[cfg_attr(feature = "ffi", ffi_export)] impl NewDomain { /// Add [`logo`](IpfsPath) to the domain replacing previously defined value #[must_use] @@ -172,8 +173,9 @@ impl NewDomain { Serialize, IntoSchema, )] +#[cfg_attr(feature = "ffi", derive(IntoFfi, TryFromFfi))] +#[cfg_attr(feature = "ffi", ffi_export)] #[allow(clippy::multiple_inherent_impl)] -#[cfg_attr(feature = "ffi_api", ffi_bindgen)] #[display(fmt = "[{id}]")] pub struct Domain { /// Identification of this [`Domain`]. @@ -183,9 +185,11 @@ pub struct Domain { /// [`Asset`](AssetDefinition)s defined of the `Domain`. asset_definitions: AssetDefinitionsMap, /// IPFS link to the `Domain` logo - #[getset(get = "pub")] + // FIXME: Getter implemented manually because `getset` + // returns &Option when it should return Option<&T> logo: Option, /// [`Metadata`] of this `Domain` as a key-value store. + #[getset(get = "pub")] #[cfg_attr(feature = "mutable_api", getset(get_mut = "pub"))] metadata: Metadata, } @@ -223,13 +227,18 @@ impl Ord for Domain { } } -#[cfg_attr(feature = "ffi_api", ffi_bindgen)] +#[cfg_attr(feature = "ffi", ffi_export)] impl Domain { /// Construct builder for [`Domain`] identifiable by [`Id`]. pub fn new(id: ::Id) -> ::With { ::With::new(id) } + /// IPFS link to the `Domain` logo + pub fn logo(&self) -> Option<&IpfsPath> { + self.logo.as_ref() + } + /// Return a reference to the [`Account`] corresponding to the account id. #[inline] pub fn account(&self, account_id: &::Id) -> Option<&Account> { @@ -341,6 +350,7 @@ impl FromIterator for crate::Value { /// Represents path in IPFS. Performs checks to ensure path validity. /// Construct using [`FromStr::from_str`] method. #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Encode, Serialize, IntoSchema)] +#[cfg_attr(feature = "ffi", derive(IntoFfi, TryFromFfi))] pub struct IpfsPath(ConstString); impl FromStr for IpfsPath { @@ -444,6 +454,7 @@ impl Decode for IpfsPath { Serialize, IntoSchema, )] +#[cfg_attr(feature = "ffi", derive(IntoFfi, TryFromFfi))] #[display(fmt = "{name}")] pub struct Id { /// [`Name`] unique to a [`Domain`] e.g. company name diff --git a/data_model/src/lib.rs b/data_model/src/lib.rs index 74a1e64b97b..9d17d814882 100644 --- a/data_model/src/lib.rs +++ b/data_model/src/lib.rs @@ -23,6 +23,8 @@ use derive_more::Into; use derive_more::{AsRef, Deref, Display, From}; use events::FilterBox; use iroha_crypto::{Hash, PublicKey}; +#[cfg(feature = "ffi")] +use iroha_ffi::{IntoFfi, TryFromFfi}; use iroha_macro::{error::ErrorTryFromEnum, FromVariant}; use iroha_primitives::{fixed, small, small::SmallVec}; use iroha_schema::{IntoSchema, MetaMap}; @@ -284,16 +286,18 @@ pub type ValueBox = Box; Clone, PartialEq, Eq, + PartialOrd, + Ord, Decode, Encode, Deserialize, Serialize, FromVariant, IntoSchema, - PartialOrd, - Ord, )] +#[cfg_attr(feature = "ffi", derive(IntoFfi, TryFromFfi))] #[allow(clippy::enum_variant_names)] +#[repr(u8)] pub enum Value { /// [`u32`] integer. U32(u32), @@ -790,8 +794,8 @@ pub fn current_time() -> core::time::Duration { .expect("Failed to get the current system time") } -#[cfg(feature = "ffi_api")] -mod ffi { +#[cfg(feature = "ffi")] +pub(crate) mod ffi { use iroha_ffi::{gen_ffi_impl, handles}; use super::*; @@ -804,13 +808,9 @@ mod ffi { permissions::PermissionToken, role::Role, Name, - - iroha_crypto::PublicKey, - iroha_crypto::PrivateKey, - iroha_crypto::KeyPair } - gen_ffi_impl! { Clone: + gen_ffi_impl! { pub Clone: account::Account, asset::Asset, domain::Domain, @@ -818,12 +818,8 @@ mod ffi { permissions::PermissionToken, role::Role, Name, - - iroha_crypto::PublicKey, - iroha_crypto::PrivateKey, - iroha_crypto::KeyPair } - gen_ffi_impl! { Eq: + gen_ffi_impl! { pub Eq: account::Account, asset::Asset, domain::Domain, @@ -831,22 +827,16 @@ mod ffi { permissions::PermissionToken, role::Role, Name, - - iroha_crypto::PublicKey, - iroha_crypto::PrivateKey, - iroha_crypto::KeyPair } - gen_ffi_impl! { Ord: + gen_ffi_impl! { pub Ord: account::Account, asset::Asset, domain::Domain, permissions::PermissionToken, role::Role, Name, - - iroha_crypto::PublicKey } - gen_ffi_impl! { Drop: + gen_ffi_impl! { pub Drop: account::Account, asset::Asset, domain::Domain, @@ -854,10 +844,6 @@ mod ffi { permissions::PermissionToken, role::Role, Name, - - iroha_crypto::PublicKey, - iroha_crypto::PrivateKey, - iroha_crypto::KeyPair } } diff --git a/data_model/src/metadata.rs b/data_model/src/metadata.rs index 03fee966ddc..227ba1a4876 100644 --- a/data_model/src/metadata.rs +++ b/data_model/src/metadata.rs @@ -8,8 +8,8 @@ use core::borrow::Borrow; use std::collections::btree_map; use derive_more::Display; -#[cfg(feature = "ffi_api")] -use iroha_ffi::ffi_bindgen; +#[cfg(feature = "ffi")] +use iroha_ffi::{IntoFfi, TryFromFfi}; use iroha_schema::IntoSchema; use parity_scale_codec::{Decode, Encode}; use serde::{Deserialize, Serialize}; @@ -80,14 +80,15 @@ impl Limits { Default, PartialEq, Eq, + PartialOrd, + Ord, Decode, Encode, Deserialize, Serialize, IntoSchema, - PartialOrd, - Ord, )] +#[cfg_attr(feature = "ffi", derive(IntoFfi, TryFromFfi))] #[serde(transparent)] #[allow(clippy::multiple_inherent_impl)] #[display(fmt = "Metadata")] @@ -98,7 +99,6 @@ pub struct Metadata { /// A path slice, composed of [`Name`]s. pub type Path = [Name]; -#[cfg_attr(feature = "ffi_api", ffi_bindgen)] impl Metadata { /// Constructor. #[inline] diff --git a/data_model/src/name.rs b/data_model/src/name.rs index 2d3125bfe36..c8517cc36c7 100644 --- a/data_model/src/name.rs +++ b/data_model/src/name.rs @@ -1,10 +1,12 @@ //! This module contains [`Name`](`crate::name::Name`) structure //! and related implementations and trait implementations. #[cfg(not(feature = "std"))] -use alloc::{format, string::String, vec::Vec}; +use alloc::{boxed::Box, format, string::String, vec::Vec}; use core::{ops::RangeInclusive, str::FromStr}; use derive_more::{DebugCustom, Display}; +#[cfg(feature = "ffi")] +use iroha_ffi::{IntoFfi, TryFromFfi}; use iroha_primitives::conststr::ConstString; use iroha_schema::IntoSchema; use parity_scale_codec::{Decode, Encode, Input}; @@ -18,7 +20,9 @@ use crate::{ParseError, ValidationError}; #[derive( DebugCustom, Display, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Encode, Serialize, IntoSchema, )] +#[cfg_attr(feature = "ffi", derive(IntoFfi, TryFromFfi))] #[repr(transparent)] +// TODO: This struct doesn't have to be opaque pub struct Name(ConstString); impl Name { @@ -31,7 +35,7 @@ impl Name { range: impl Into>, ) -> Result<(), ValidationError> { let range = range.into(); - if range.contains(&self.as_ref().chars().count()) { + if range.contains(&self.0.chars().count()) { Ok(()) } else { Err(ValidationError::new(&format!( @@ -86,27 +90,40 @@ impl FromStr for Name { /// # Safety /// /// All of the given pointers must be valid -#[cfg(feature = "ffi_api")] +#[no_mangle] +#[cfg(feature = "ffi")] #[allow(non_snake_case, unsafe_code)] -pub unsafe extern "C" fn Name__from_str( - candidate: *const u8, - candidate_len: usize, - output: *mut *mut Name, +pub unsafe extern "C" fn Name__from_str<'itm>( + candidate: <&'itm str as iroha_ffi::TryFromReprC<'itm>>::Source, + out_ptr: <::Target as iroha_ffi::Output>::OutPtr, ) -> iroha_ffi::FfiResult { - let candidate = core::slice::from_raw_parts(candidate, candidate_len); - - let method_res = match core::str::from_utf8(candidate) { - Err(_error) => return iroha_ffi::FfiResult::Utf8Error, - Ok(candidate) => Name::from_str(candidate), - }; - let method_res = match method_res { - Err(_error) => return iroha_ffi::FfiResult::ExecutionFail, - Ok(method_res) => method_res, - }; - let method_res = Box::into_raw(Box::new(method_res)); - - output.write(method_res); - iroha_ffi::FfiResult::Ok + let res = std::panic::catch_unwind(|| { + // False positive - doesn't compile otherwise + #[allow(clippy::let_unit_value)] + let fn_body = || { + let mut store = Default::default(); + let candidate: &str = iroha_ffi::TryFromReprC::try_from_repr_c(candidate, &mut store)?; + let method_res = Name::from_str(candidate) + .map_err(|_e| iroha_ffi::FfiResult::ExecutionFail)? + .into_ffi(); + iroha_ffi::OutPtrOf::write(out_ptr, method_res)?; + Ok(()) + }; + + if let Err(err) = fn_body() { + return err; + } + + iroha_ffi::FfiResult::Ok + }); + + match res { + Ok(res) => res, + Err(_) => { + // TODO: Implement error handling (https://github.com/hyperledger/iroha/issues/2252) + iroha_ffi::FfiResult::UnrecoverableError + } + } } impl<'de> Deserialize<'de> for Name { @@ -168,31 +185,27 @@ mod tests { #[test] #[allow(unsafe_code)] - #[cfg(feature = "ffi_api")] + #[cfg(feature = "ffi")] fn ffi_name_from_str() -> Result<(), ParseError> { - use crate::ffi::{Handle, __drop}; - + use iroha_ffi::Handle; let candidate = "Name"; - let candidate_bytes = candidate.as_bytes(); - let candidate_bytes_len = candidate_bytes.len(); unsafe { let mut name = core::mem::MaybeUninit::new(core::ptr::null_mut()); assert_eq!( iroha_ffi::FfiResult::Ok, - Name__from_str( - candidate_bytes.as_ptr(), - candidate_bytes_len, - name.as_mut_ptr() - ) + Name__from_str(candidate.into_ffi(), name.as_mut_ptr()) ); let name = name.assume_init(); assert_ne!(core::ptr::null_mut(), name); assert_eq!(Name::from_str(candidate)?, *name); - assert_eq!(iroha_ffi::FfiResult::Ok, __drop(Name::ID, name.cast())); + assert_eq!( + iroha_ffi::FfiResult::Ok, + crate::ffi::__drop(Name::ID, name.cast()) + ); } Ok(()) diff --git a/data_model/src/permissions.rs b/data_model/src/permissions.rs index 66221fef695..91c525ebe03 100644 --- a/data_model/src/permissions.rs +++ b/data_model/src/permissions.rs @@ -2,6 +2,7 @@ #[cfg(not(feature = "std"))] use alloc::{ + boxed::Box, collections::{btree_map, btree_set}, format, string::String, @@ -12,6 +13,8 @@ use std::collections::{btree_map, btree_set}; use derive_more::Display; use getset::Getters; +#[cfg(feature = "ffi")] +use iroha_ffi::{ffi_export, IntoFfi, TryFromFfi}; use iroha_schema::IntoSchema; use parity_scale_codec::{Decode, Encode}; use serde::{Deserialize, Serialize}; @@ -28,17 +31,18 @@ pub type Permissions = btree_set::BTreeSet; Clone, PartialEq, Eq, + PartialOrd, + Ord, Getters, Decode, Encode, Deserialize, Serialize, IntoSchema, - PartialOrd, - Ord, )] +#[cfg_attr(feature = "ffi", derive(IntoFfi, TryFromFfi))] +#[cfg_attr(feature = "ffi", ffi_export)] #[getset(get = "pub")] -#[cfg_attr(feature = "ffi_api", iroha_ffi::ffi_bindgen)] #[display(fmt = "{name}")] pub struct PermissionToken { /// Name of the permission rule given to account. @@ -48,7 +52,6 @@ pub struct PermissionToken { params: btree_map::BTreeMap, } -#[cfg_attr(feature = "ffi_api", iroha_ffi::ffi_bindgen)] impl PermissionToken { /// Constructor. #[inline] diff --git a/data_model/src/role.rs b/data_model/src/role.rs index cc669982c42..e3e6e76442c 100644 --- a/data_model/src/role.rs +++ b/data_model/src/role.rs @@ -1,14 +1,14 @@ //! Structures, traits and impls related to `Role`s. #[cfg(not(feature = "std"))] -use alloc::{collections::btree_set, format, string::String, vec::Vec}; +use alloc::{boxed::Box, collections::btree_set, format, string::String, vec::Vec}; #[cfg(feature = "std")] use std::collections::btree_set; use derive_more::{Constructor, Display, FromStr}; use getset::Getters; -#[cfg(feature = "ffi_api")] -use iroha_ffi::ffi_bindgen; +#[cfg(feature = "ffi")] +use iroha_ffi::{ffi_export, IntoFfi, TryFromFfi}; use iroha_schema::IntoSchema; use parity_scale_codec::{Decode, Encode}; use serde::{Deserialize, Serialize}; @@ -39,6 +39,7 @@ pub type RoleIds = btree_set::BTreeSet<::Id>; Serialize, IntoSchema, )] +#[cfg_attr(feature = "ffi", derive(IntoFfi, TryFromFfi))] pub struct Id { /// Role name, should be unique . pub name: Name, @@ -58,7 +59,8 @@ pub struct Id { Serialize, IntoSchema, )] -#[cfg_attr(feature = "ffi_api", ffi_bindgen)] +#[cfg_attr(feature = "ffi", derive(IntoFfi, TryFromFfi))] +#[cfg_attr(feature = "ffi", ffi_export)] #[display(fmt = "{id}")] #[getset(get = "pub")] pub struct Role { @@ -84,7 +86,7 @@ impl Ord for Role { } } -#[cfg_attr(feature = "ffi_api", ffi_bindgen)] +#[cfg_attr(feature = "ffi", ffi_export)] impl Role { /// Constructor. #[inline] @@ -125,6 +127,8 @@ impl Registered for Role { Serialize, IntoSchema, )] +#[cfg_attr(feature = "ffi", derive(IntoFfi, TryFromFfi))] +#[allow(clippy::multiple_inherent_impl)] pub struct NewRole { inner: Role, } @@ -154,7 +158,17 @@ impl Ord for NewRole { } } -/// Builder for [`Role`] +#[cfg_attr(feature = "ffi", ffi_export)] +impl NewRole { + /// Add permission to the [`Role`] + #[must_use] + #[inline] + pub fn add_permission(mut self, perm: impl Into) -> Self { + self.inner.permissions.insert(perm.into()); + self + } +} + impl NewRole { /// Constructor #[must_use] @@ -170,17 +184,9 @@ impl NewRole { /// Identification #[inline] - pub fn id(&self) -> &::Id { + pub(crate) fn id(&self) -> &::Id { &self.inner.id } - - /// Add permission to the [`Role`] - #[must_use] - #[inline] - pub fn add_permission(mut self, perm: impl Into) -> Self { - self.inner.permissions.insert(perm.into()); - self - } } /// The prelude re-exports most commonly used traits, structs and macros from this module. diff --git a/ffi/derive/Cargo.toml b/ffi/derive/Cargo.toml index 245ee14a834..41ae38567ca 100644 --- a/ffi/derive/Cargo.toml +++ b/ffi/derive/Cargo.toml @@ -12,6 +12,7 @@ syn = { version = "1.0", features = ["full", "visit", "visit-mut", "extra-traits quote = "1.0" proc-macro2 = "1.0" proc-macro-error = "1.0" +derive_more = "0.99.17" [dev-dependencies] iroha_ffi = { version = "=2.0.0-pre-rc.5", path = "../" } diff --git a/ffi/derive/src/bindgen.rs b/ffi/derive/src/bindgen.rs deleted file mode 100644 index 101a66e5935..00000000000 --- a/ffi/derive/src/bindgen.rs +++ /dev/null @@ -1,474 +0,0 @@ -use proc_macro2::Span; -use proc_macro_error::{abort, OptionExt}; -use quote::quote; -use syn::{parse_quote, Ident, Type}; - -use crate::{ - get_ident, - impl_visitor::{FnArgDescriptor, FnDescriptor}, -}; - -pub fn gen_ffi_fn(fn_descriptor: &FnDescriptor) -> proc_macro2::TokenStream { - let ffi_fn_name = gen_ffi_fn_name(fn_descriptor); - - let self_arg = fn_descriptor - .receiver - .as_ref() - .map(gen_ffi_fn_arg) - .map_or_else(Vec::new, |self_arg| vec![self_arg]); - let fn_args: Vec<_> = fn_descriptor - .input_args - .iter() - .map(gen_ffi_fn_arg) - .collect(); - let output_arg = ffi_output_arg(fn_descriptor).map(gen_ffi_fn_arg); - let fn_body = gen_fn_body(fn_descriptor); - - let ffi_fn_doc = format!( - " FFI function equivalent of [`{}::{}`]\n \ - \n \ - # Safety\n \ - \n \ - All of the given pointers must be valid", - fn_descriptor.self_ty.get_ident().expect_or_abort("Defined"), - fn_descriptor.method_name - ); - - quote! { - #[doc = #ffi_fn_doc] - #[no_mangle] - pub unsafe extern "C" fn #ffi_fn_name(#(#self_arg,)* #(#fn_args,)* #output_arg) -> iroha_ffi::FfiResult { - #fn_body - } - } -} - -fn gen_ffi_fn_name(fn_descriptor: &FnDescriptor) -> Ident { - let self_ty_name = fn_descriptor.self_ty_name(); - - Ident::new( - &format!("{}__{}", self_ty_name, fn_descriptor.method_name), - Span::call_site(), - ) -} - -fn gen_fn_body(fn_descriptor: &FnDescriptor) -> syn::Block { - let checks = gen_type_check_stmts(fn_descriptor); - let input_conversions = gen_ffi_to_src_conversion_stmts(fn_descriptor); - let method_call_stmt = gen_method_call_stmt(fn_descriptor); - let output_conversions = gen_src_to_ffi_conversion_stmts(fn_descriptor); - let output_assignment = gen_output_assignment_stmts(fn_descriptor); - - parse_quote! {{ - #( #checks )* - #( #input_conversions )* - - #method_call_stmt - - #( #output_conversions )* - #( #output_assignment )* - - iroha_ffi::FfiResult::Ok - }} -} - -fn gen_type_check_stmts(fn_descriptor: &FnDescriptor) -> Vec { - let mut stmts = gen_dangling_ptr_assignments(fn_descriptor); - - fn_descriptor - .receiver - .as_ref() - .map(|self_arg| gen_ptr_null_check_stmt(self_arg).map(|stmt| stmts.push(stmt))); - - for arg in &fn_descriptor.input_args { - if let Some(stmt) = gen_ptr_null_check_stmt(arg) { - stmts.push(stmt); - } - } - - if let Some(output_arg) = ffi_output_arg(fn_descriptor) { - if let Some(stmt) = gen_ptr_null_check_stmt(output_arg) { - stmts.push(stmt); - } - if output_arg.is_slice_ref_mut() { - let slice_elems_arg_name = gen_slice_elems_arg_name(output_arg); - - stmts.push(parse_quote! { - if #slice_elems_arg_name.is_null() { - return iroha_ffi::FfiResult::ArgIsNull; - } - }); - } - } - - stmts -} - -fn gen_ffi_to_src_conversion_stmts(fn_descriptor: &FnDescriptor) -> Vec { - let mut stmts = vec![]; - - if let Some(self_arg) = &fn_descriptor.receiver { - let arg_name = &self_arg.ffi_name; - - match &self_arg.src_type { - Type::Path(_) => stmts.push(parse_quote! { - let _handle = #arg_name.read(); - }), - Type::Reference(type_) => { - stmts.push(if type_.mutability.is_some() { - parse_quote! { let #arg_name = &mut *#arg_name; } - } else { - parse_quote! { let #arg_name = &*#arg_name; } - }); - } - _ => unreachable!("Self can only be taken by value or by reference"), - } - } - - for arg in &fn_descriptor.input_args { - stmts.extend(gen_ffi_to_src_arg_conversion_stmts(arg)); - } - - stmts -} - -fn gen_method_call_stmt(fn_descriptor: &FnDescriptor) -> syn::Stmt { - let method_name = fn_descriptor.method_name; - let self_type = fn_descriptor.self_ty; - - let receiver = fn_descriptor.receiver.as_ref(); - let self_arg_name = receiver.map_or_else(Vec::new, |arg| { - if matches!(arg.src_type, Type::Path(_)) { - return vec![Ident::new("_handle", Span::call_site())]; - } - - vec![arg.ffi_name.clone()] - }); - - let fn_arg_names = fn_descriptor.input_args.iter().map(|arg| &arg.ffi_name); - parse_quote! { let method_res = #self_type::#method_name(#(#self_arg_name,)* #(#fn_arg_names),*); } -} - -fn gen_src_to_ffi_conversion_stmts(fn_descriptor: &FnDescriptor) -> Vec { - if let Some(output_arg) = ffi_output_arg(fn_descriptor) { - return gen_src_to_ffi_arg_conversion_stmts(output_arg); - } - - vec![] -} - -fn ffi_output_arg<'tmp: 'ast, 'ast>( - fn_descriptor: &'tmp FnDescriptor<'ast>, -) -> Option<&'ast FnArgDescriptor> { - fn_descriptor.output_arg.as_ref().and_then(|output_arg| { - if let Some(receiver) = &fn_descriptor.receiver { - if receiver.ffi_name == output_arg.ffi_name { - return None; - } - } - - Some(output_arg) - }) -} - -fn gen_output_assignment_stmts(fn_descriptor: &FnDescriptor) -> Vec { - let mut stmts = vec![]; - - if let Some(output_arg) = &fn_descriptor.output_arg { - let output_arg_name = &output_arg.ffi_name; - - if output_arg.is_slice_ref_mut() { - let (slice_len_arg_name, slice_elems_arg_name) = ( - gen_slice_len_arg_name(output_arg), - gen_slice_elems_arg_name(output_arg), - ); - - stmts.push(parse_quote! {{ - let #output_arg_name = core::slice::from_raw_parts_mut(#output_arg_name, #slice_len_arg_name); - - #slice_elems_arg_name.write(method_res.len()); - for (i, elem) in method_res.take(#slice_len_arg_name).enumerate() { - #output_arg_name[i] = elem; - } - }}); - } else { - assert!(matches!(output_arg.ffi_type, Type::Ptr(_))); - stmts.push(parse_quote! { #output_arg_name.write(method_res); }); - } - } - - stmts -} - -fn gen_ffi_fn_arg(fn_arg_descriptor: &FnArgDescriptor) -> proc_macro2::TokenStream { - let ffi_name = &fn_arg_descriptor.ffi_name; - let src_type = &fn_arg_descriptor.src_type; - let ffi_type = &fn_arg_descriptor.ffi_type; - - if fn_arg_descriptor.is_slice_ref() || fn_arg_descriptor.is_slice_ref_mut() { - let mut tokens = quote! { #ffi_name: #ffi_type, }; - slice_len_arg_to_tokens(src_type, fn_arg_descriptor, &mut tokens); - tokens - } else { - quote! { #ffi_name: #ffi_type } - } -} - -fn gen_slice_elems_arg_name(fn_arg_descriptor: &FnArgDescriptor) -> Ident { - Ident::new( - &format!("{}_elems", fn_arg_descriptor.ffi_name), - Span::call_site(), - ) -} - -fn slice_len_arg_to_tokens( - src_type: &Type, - ffi_fn_arg: &FnArgDescriptor, - tokens: &mut proc_macro2::TokenStream, -) { - let mut slice_len_to_tokens = || { - let slice_len_arg_name = gen_slice_len_arg_name(ffi_fn_arg); - tokens.extend(quote! { #slice_len_arg_name: usize }); - }; - - match &src_type { - Type::Reference(type_) => { - if matches!(*type_.elem, Type::Slice(_)) { - slice_len_to_tokens(); - } - } - 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(); - } else if last_seg.ident == "ExactSizeIterator" { - slice_len_to_tokens(); - let slice_elems_arg_name = gen_slice_elems_arg_name(ffi_fn_arg); - tokens.extend(quote! {, #slice_elems_arg_name: *mut usize }); - } else { - abort!(src_type, "Unsupported impl trait slice type") - } - } - } - _ => {} - } -} - -/// Returns a null check statement for this argument if it's FFI type is [`Type::Ptr`] -fn gen_ptr_null_check_stmt(fn_arg_descriptor: &FnArgDescriptor) -> Option { - let arg_name = &fn_arg_descriptor.ffi_name; - - if fn_arg_descriptor.is_ffi_ptr() { - return Some(parse_quote! { - if #arg_name.is_null() { - return iroha_ffi::FfiResult::ArgIsNull; - } - }); - } - - None -} - -fn gen_dangling_ptr_assignments(fn_descriptor: &FnDescriptor) -> Vec { - let mut stmts = vec![]; - - for arg in &fn_descriptor.input_args { - if arg.is_slice_ref() { - stmts.push(gen_dangling_ptr_assignment(arg)); - } - } - if let Some(output_arg) = ffi_output_arg(fn_descriptor) { - if output_arg.is_slice_ref_mut() { - stmts.push(gen_dangling_ptr_assignment(output_arg)); - } - } - - stmts -} - -// NOTE: `slice::from_raw_parts` takes a non-null aligned pointer -fn gen_dangling_ptr_assignment(fn_arg_descriptor: &FnArgDescriptor) -> syn::Stmt { - let (arg_name, slice_len_arg_name) = ( - &fn_arg_descriptor.ffi_name, - gen_slice_len_arg_name(fn_arg_descriptor), - ); - - parse_quote! { - let #arg_name = if #slice_len_arg_name == 0_usize { - core::ptr::NonNull::dangling().as_ptr() - } else { #arg_name }; - } -} - -fn gen_slice_len_arg_name(fn_arg_descriptor: &FnArgDescriptor) -> Ident { - Ident::new( - &format!("{}_len", fn_arg_descriptor.ffi_name), - Span::call_site(), - ) -} - -fn gen_ffi_to_src_impl_into_iterator_conversion_stmts( - fn_arg_descriptor: &FnArgDescriptor, - ffi_type: &syn::TypePtr, -) -> Vec { - let slice_len_arg_name = gen_slice_len_arg_name(fn_arg_descriptor); - - let arg_name = &fn_arg_descriptor.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)| { - (Clone::clone(&*key), Clone::clone(&*val)) - }); - }); - } else { - abort!(last_seg, "Collection item not supported in FFI") - } - } - Type::Ptr(_) => { - stmts.push(parse_quote! { - let #arg_name = #arg_name.map(|&ptr| Clone::clone(&*ptr)); - }); - } - _ => abort!(fn_arg_descriptor.src_type, "Unsupported FFI type"), - } - - stmts -} - -fn gen_ffi_to_src_arg_conversion_stmts(fn_arg_descriptor: &FnArgDescriptor) -> Vec { - let mut stmts = vec![]; - - let arg_name = &fn_arg_descriptor.ffi_name; - match (&fn_arg_descriptor.src_type, &fn_arg_descriptor.ffi_type) { - (Type::Reference(src_ty), Type::Ptr(_)) => { - if matches!(*src_ty.elem, Type::Slice(_)) { - // TODO: slice is here - } else { - stmts.push(parse_quote! { let #arg_name = &*#arg_name; }); - } - } - (Type::ImplTrait(src_ty), Type::Ptr(ffi_ty)) => { - if let syn::TypeParamBound::Trait(trait_) = &src_ty.bounds[0] { - let last_seg = &trait_.path.segments.last().expect_or_abort("Defined"); - - match last_seg.ident.to_string().as_ref() { - "IntoIterator" => { - stmts.extend(gen_ffi_to_src_impl_into_iterator_conversion_stmts( - fn_arg_descriptor, - ffi_ty, - )) - } - "Into" => stmts.push(parse_quote! { - let #arg_name = Clone::clone(&*#arg_name); - }), - _ => abort!(last_seg, "impl Trait type not supported"), - } - } - } - (Type::Path(_), Type::Ptr(_)) => { - stmts.push(parse_quote! { let #arg_name = Clone::clone(&*#arg_name); }); - } - (Type::Path(src_ty), Type::Path(_)) => { - let last_seg = src_ty.path.segments.last().expect_or_abort("Defined"); - - match last_seg.ident.to_string().as_ref() { - "bool" => stmts.push(parse_quote! { let #arg_name = #arg_name != 0; }), - // TODO: Wasm conversions? - _ => unreachable!("Unsupported FFI conversion"), - } - } - _ => abort!(fn_arg_descriptor.src_type, "Unsupported FFI type"), - } - - stmts -} - -fn gen_src_to_ffi_arg_conversion_stmts(fn_arg_descriptor: &FnArgDescriptor) -> Vec { - let ffi_type = if let Type::Ptr(ffi_type) = &fn_arg_descriptor.ffi_type { - &*ffi_type.elem - } else { - unreachable!("Output must be an out-pointer") - }; - - let mut stmts = vec![]; - match (&fn_arg_descriptor.src_type, ffi_type) { - (Type::Reference(src_ty), Type::Ptr(_)) => { - stmts.push(if src_ty.mutability.is_some() { - parse_quote! { let method_res: *mut _ = method_res; } - } else { - parse_quote! { let method_res: *const _ = method_res; } - }); - } - (Type::ImplTrait(_), Type::Path(ffi_ty)) => { - if ffi_ty.path.segments.last().expect_or_abort("Defined").ident != "Pair" { - abort!(fn_arg_descriptor.src_type, "Unsupported FFI type"); - } - - stmts.push(parse_quote! { - let method_res = method_res.into_iter().map(|(key, val)| { - iroha_ffi::Pair(key as *const _, val as *const _) - }); - }); - } - (Type::ImplTrait(_), Type::Ptr(ffi_ty)) => { - stmts.push(parse_quote! { let method_res = method_res.into_iter(); }); - - if !matches!(*ffi_ty.elem, Type::Path(_)) { - abort!(fn_arg_descriptor.src_type, "Unsupported FFI type"); - } - - stmts.push(if ffi_ty.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 _); } - }); - } - (Type::Path(src_ty), Type::Ptr(ffi_ty)) => { - let is_option_type = get_ident(&src_ty.path) == "Option"; - - stmts.push(if is_option_type && ffi_ty.mutability.is_some() { - parse_quote! { - let method_res = method_res.map_or(core::ptr::null_mut(), |elem| elem as *mut _); - } - } else if is_option_type && ffi_ty.mutability.is_none() { - parse_quote! { - let method_res = method_res.map_or(core::ptr::null(), |elem| elem as *const _); - } - } else { - parse_quote! { let method_res = Box::into_raw(Box::new(method_res)); } - }); - } - (Type::Path(src_ty), Type::Path(_)) => { - let last_seg = src_ty.path.segments.last().expect_or_abort("Defined"); - - match last_seg.ident.to_string().as_ref() { - "bool" => stmts.push(parse_quote! { let method_res = method_res as u8; }), - "Result" => stmts.push(parse_quote! { - let method_res = match method_res { - Ok(method_res) => method_res, - Err(error) => { - return iroha_ffi::FfiResult::ExecutionFail; - } - }; - }), - // TODO: Wasm conversions? - _ => unreachable!("Unsupported FFI conversion"), - } - } - _ => abort!(fn_arg_descriptor.src_type, "Unsupported FFI type"), - } - - stmts -} diff --git a/ffi/derive/src/derive.rs b/ffi/derive/src/derive.rs index f323cfeecf2..4bb40243f29 100644 --- a/ffi/derive/src/derive.rs +++ b/ffi/derive/src/derive.rs @@ -3,16 +3,19 @@ use std::collections::HashSet; use proc_macro2::TokenStream; use proc_macro_error::{abort, OptionExt}; use quote::quote; -use syn::{parse_quote, visit_mut::VisitMut, Ident, ItemStruct, Type}; +use syn::{parse_quote, Ident, ItemStruct}; -use crate::{get_ident, impl_visitor::SelfResolver}; +use crate::{ + export::{gen_arg_ffi_to_src, gen_arg_src_to_ffi}, + impl_visitor::{Arg, InputArg, Receiver, ReturnArg}, +}; /// Type of accessor method derived for a structure #[derive(Clone, Copy, PartialEq, Eq, Hash)] enum Derive { - Set, - Get, - GetMut, + Setter, + Getter, + MutGetter, } /// Generate FFI function equivalents of derived methods @@ -56,11 +59,11 @@ fn parse_derives(attrs: &[syn::Attribute]) -> Option> { syn::Meta::NameValue(item) => { if item.lit == parse_quote! {"pub"} { if item.path.is_ident("set") { - acc.insert(Derive::Set); + acc.insert(Derive::Setter); } else if item.path.is_ident("get") { - acc.insert(Derive::Get); + acc.insert(Derive::Getter); } else if item.path.is_ident("get_mut") { - acc.insert(Derive::GetMut); + acc.insert(Derive::MutGetter); } } } @@ -80,9 +83,9 @@ fn parse_derives(attrs: &[syn::Attribute]) -> Option> { fn gen_derive_method_name(field_name: &Ident, derive: Derive) -> syn::Ident { Ident::new( &match derive { - Derive::Set => format!("set_{}", field_name), - Derive::Get => format!("{}", field_name), - Derive::GetMut => format!("{}_mut", field_name), + Derive::Setter => format!("set_{}", field_name), + Derive::Getter => format!("{}", field_name), + Derive::MutGetter => format!("{}_mut", field_name), }, proc_macro2::Span::call_site(), ) @@ -103,122 +106,128 @@ fn gen_ffi_fn_name(struct_name: &Ident, derive_method_name: &syn::Ident) -> syn: ) } -fn gen_null_ptr_check(arg: &Ident) -> TokenStream { - quote! { - if #arg.is_null() { - return iroha_ffi::FfiResult::ArgIsNull; - } - } -} - -fn gen_ffi_fn_args(struct_name: &Ident, mut field_ty: &Type, derive: Derive) -> TokenStream { - if let Type::Path(ty) = field_ty { - let last_seg = &ty.path.segments.last().expect_or_abort("Defined"); - - if last_seg.ident == "Option" { - field_ty = crate::impl_visitor::generic_arg_types(last_seg)[0]; - } - } +fn gen_ffi_fn_args(handle: &Receiver, field: &impl Arg, derive: Derive) -> TokenStream { + let (handle_name, handle_type) = (&handle.name(), handle.ffi_type_resolved()); + let (field_name, field_type) = (&field.name(), field.ffi_type_resolved()); match derive { - Derive::Set => { - quote! {handle: *mut #struct_name, field: *const #field_ty} - } - Derive::Get => { - quote! {handle: *const #struct_name, output: *mut *const #field_ty} - } - Derive::GetMut => { - quote! {handle: *mut #struct_name, output: *mut *mut #field_ty} - } + Derive::Setter => quote! { + #handle_name: #handle_type, #field_name: #field_type, + }, + Derive::Getter | Derive::MutGetter => quote! { + #handle_name: #handle_type, #field_name: <#field_type as iroha_ffi::Output>::OutPtr + }, } } -fn gen_option_ptr_conversion(field_ty: &Type, derive: Derive) -> Option { - if let Type::Path(ty) = field_ty { - if get_ident(&ty.path) == "Option" { - return match derive { - Derive::Set => None, - Derive::Get => Some(quote! { - let method_res = match method_res { - Some(method_res) => method_res, - None => core::ptr::null(), - }; - }), - Derive::GetMut => Some(quote! { - let method_res = match method_res { - Some(method_res) => method_res, - None => core::ptr::null_mut(), - }; - }), - }; - } - } +fn gen_ffi_fn_body( + method_name: &Ident, + handle_arg: &Receiver, + field_arg: &impl Arg, + derive: Derive, +) -> TokenStream { + let (handle_name, into_handle) = (handle_arg.name(), gen_arg_ffi_to_src(handle_arg, false)); - None -} + match derive { + Derive::Setter => { + let (field_name, into_field) = (field_arg.name(), gen_arg_ffi_to_src(field_arg, false)); -fn gen_ffi_fn_body(method_name: &Ident, field_ty: &Type, derive: Derive) -> TokenStream { - let mut null_ptr_checks = vec![gen_null_ptr_check(&parse_quote! {handle})]; - let option_ptr_conversion = gen_option_ptr_conversion(field_ty, derive); + quote! {{ + #into_handle + #into_field - match derive { - Derive::Set => { - null_ptr_checks.push(gen_null_ptr_check(&parse_quote! {field})); - - quote! { - #( #null_ptr_checks )* - let handle = &mut *handle; - let field = (&*field).clone(); - handle.#method_name(field); - iroha_ffi::FfiResult::Ok - } + #handle_name.#method_name(#field_name); + Ok(()) + }} } - Derive::Get => { - null_ptr_checks.push(gen_null_ptr_check(&parse_quote! {output})); - - quote! { - #( #null_ptr_checks )* - let handle = &*handle; - let method_res = handle.#method_name(); - #option_ptr_conversion - output.write(method_res); - iroha_ffi::FfiResult::Ok - } - } - Derive::GetMut => { - null_ptr_checks.push(gen_null_ptr_check(&parse_quote! {output})); - - quote! { - #( #null_ptr_checks )* - let handle = &mut *handle; - let method_res = handle.#method_name(); - #option_ptr_conversion - output.write(method_res); - iroha_ffi::FfiResult::Ok - } + Derive::Getter | Derive::MutGetter => { + let (field_name, from_field) = (field_arg.name(), gen_arg_src_to_ffi(field_arg, true)); + + quote! {{ + #into_handle + + let __out_ptr = #field_name; + let #field_name = #handle_name.#method_name(); + #from_field + iroha_ffi::OutPtrOf::write(__out_ptr, #field_name)?; + Ok(()) + }} } } } -fn gen_ffi_derive(struct_name: &Ident, field: &syn::Field, derive: Derive) -> syn::ItemFn { +fn gen_ffi_derive(item_name: &Ident, field: &syn::Field, derive: Derive) -> syn::ItemFn { + let handle_name = Ident::new("__handle", proc_macro2::Span::call_site()); let field_name = field.ident.as_ref().expect_or_abort("Defined"); - - let mut field_ty = field.ty.clone(); - if let Type::Path(field_ty) = &mut field_ty { - SelfResolver::new(&parse_quote! { #struct_name }).visit_type_path_mut(field_ty); - } + let self_ty = parse_quote! {#item_name}; let derive_method_name = gen_derive_method_name(field_name, derive); - let ffi_fn_name = gen_ffi_fn_name(struct_name, &derive_method_name); - let ffi_fn_doc = gen_ffi_docs(struct_name, &derive_method_name); - let ffi_fn_args = gen_ffi_fn_args(struct_name, &field_ty, derive); - let ffi_fn_body = gen_ffi_fn_body(&derive_method_name, &field_ty, derive); + let ffi_fn_name = gen_ffi_fn_name(item_name, &derive_method_name); + let ffi_fn_doc = gen_ffi_docs(item_name, &derive_method_name); + + let field_ty = &field.ty; + let (ffi_fn_args, ffi_fn_body) = match derive { + Derive::Setter => { + let (handle_arg, field_arg) = ( + Receiver::new(&self_ty, handle_name, parse_quote! {&mut Self}), + InputArg::new(&self_ty, field_name, field_ty), + ); + + ( + gen_ffi_fn_args(&handle_arg, &field_arg, derive), + gen_ffi_fn_body(&derive_method_name, &handle_arg, &field_arg, derive), + ) + } + Derive::Getter => { + let field_ty = parse_quote! {&#field_ty}; + + let (handle_arg, field_arg) = ( + Receiver::new(&self_ty, handle_name, parse_quote! {&Self}), + ReturnArg::new(&self_ty, field_name.clone(), &field_ty), + ); + + ( + gen_ffi_fn_args(&handle_arg, &field_arg, derive), + gen_ffi_fn_body(&derive_method_name, &handle_arg, &field_arg, derive), + ) + } + Derive::MutGetter => { + let field_ty = parse_quote! {&mut #field_ty}; + + let (handle_arg, field_arg) = ( + Receiver::new(&self_ty, handle_name, parse_quote! {&mut Self}), + ReturnArg::new(&self_ty, field_name.clone(), &field_ty), + ); + + ( + gen_ffi_fn_args(&handle_arg, &field_arg, derive), + gen_ffi_fn_body(&derive_method_name, &handle_arg, &field_arg, derive), + ) + } + }; parse_quote! { #[doc = #ffi_fn_doc] #[no_mangle] - pub unsafe extern "C" fn #ffi_fn_name(#ffi_fn_args) -> iroha_ffi::FfiResult { - #ffi_fn_body + unsafe extern "C" fn #ffi_fn_name<'itm>(#ffi_fn_args) -> iroha_ffi::FfiResult { + let res = std::panic::catch_unwind(|| { + #[allow(clippy::shadow_unrelated)] + let fn_body = || #ffi_fn_body; + + if let Err(err) = fn_body() { + return err; + } + + iroha_ffi::FfiResult::Ok + }); + + match res { + Ok(res) => res, + Err(_) => { + // TODO: Implement error handling (https://github.com/hyperledger/iroha/issues/2252) + iroha_ffi::FfiResult::UnrecoverableError + }, + } } } } diff --git a/ffi/derive/src/export.rs b/ffi/derive/src/export.rs new file mode 100644 index 00000000000..51ab17e86bb --- /dev/null +++ b/ffi/derive/src/export.rs @@ -0,0 +1,276 @@ +use proc_macro2::{Span, TokenStream}; +use proc_macro_error::OptionExt; +use quote::quote; +use syn::{parse_quote, Ident, Type}; + +use crate::impl_visitor::{unwrap_result_type, Arg, FnDescriptor}; + +pub fn gen_ffi_fn(fn_descriptor: &FnDescriptor) -> TokenStream { + let ffi_fn_name = gen_ffi_fn_name(fn_descriptor); + + let self_arg = fn_descriptor + .receiver + .as_ref() + .map(gen_ffi_fn_input_arg) + .map_or_else(Vec::new, |self_arg| vec![self_arg]); + let fn_args: Vec<_> = fn_descriptor + .input_args + .iter() + .map(gen_ffi_fn_input_arg) + .collect(); + let output_arg = ffi_output_arg(fn_descriptor).map(gen_ffi_fn_out_ptr_arg); + let ffi_fn_body = gen_fn_body(fn_descriptor); + + let ffi_fn_doc = format!( + " FFI function equivalent of [`{}::{}`]\n \ + \n \ + # Safety\n \ + \n \ + All of the given pointers must be valid", + fn_descriptor.self_ty.get_ident().expect_or_abort("Defined"), + fn_descriptor.method_name + ); + + quote! { + #[doc = #ffi_fn_doc] + #[no_mangle] + unsafe extern "C" fn #ffi_fn_name<'itm>(#(#self_arg,)* #(#fn_args,)* #output_arg) -> iroha_ffi::FfiResult { + #[allow(clippy::shadow_unrelated)] + let res = std::panic::catch_unwind(|| { + let fn_body = || #ffi_fn_body; + + if let Err(err) = fn_body() { + return err; + } + + iroha_ffi::FfiResult::Ok + }); + + match res { + Ok(res) => res, + Err(_) => { + // TODO: Implement error handling (https://github.com/hyperledger/iroha/issues/2252) + iroha_ffi::FfiResult::UnrecoverableError + }, + } + } + } +} + +fn gen_ffi_fn_name(fn_descriptor: &FnDescriptor) -> Ident { + let self_ty_name = fn_descriptor.self_ty_name(); + + Ident::new( + &format!("{}__{}", self_ty_name, fn_descriptor.method_name), + Span::call_site(), + ) +} + +fn gen_fn_body(fn_descriptor: &FnDescriptor) -> syn::Block { + let input_conversions = gen_ffi_to_src_stmts(fn_descriptor); + let method_call_stmt = gen_method_call_stmt(fn_descriptor); + let output_assignment = gen_output_assignment_stmts(fn_descriptor); + + parse_quote! {{ + #input_conversions + #method_call_stmt + #output_assignment + + Ok(()) + }} +} + +fn gen_ffi_to_src_stmts(fn_descriptor: &FnDescriptor) -> TokenStream { + let mut stmts = quote! {}; + + if let Some(arg) = &fn_descriptor.receiver { + let arg_name = &arg.name(); + + stmts = if matches!(arg.src_type(), Type::Path(_)) { + quote! {let __tmp_handle = #arg_name.read();} + } else { + gen_arg_ffi_to_src(arg, false) + }; + } + + for arg in &fn_descriptor.input_args { + stmts.extend(gen_arg_ffi_to_src(arg, false)); + } + + stmts +} + +fn gen_method_call_stmt(fn_descriptor: &FnDescriptor) -> TokenStream { + let method_name = fn_descriptor.method_name; + let self_type = fn_descriptor.self_ty; + + let receiver = fn_descriptor.receiver.as_ref(); + let self_arg_name = receiver.map_or_else(Vec::new, |arg| { + if matches!(arg.src_type(), Type::Path(_)) { + return vec![Ident::new("__tmp_handle", Span::call_site())]; + } + + vec![arg.name().clone()] + }); + + let fn_arg_names = fn_descriptor.input_args.iter().map(Arg::name); + let method_call = quote! {#self_type::#method_name(#(#self_arg_name,)* #(#fn_arg_names),*)}; + + fn_descriptor.output_arg.as_ref().map_or_else( + || quote! {#method_call;}, + |output_arg| { + let output_arg_name = &output_arg.name(); + + quote! { + let __out_ptr = #output_arg_name; + let #output_arg_name = #method_call; + } + }, + ) +} + +fn ffi_output_arg<'tmp: 'ast, 'ast>( + fn_descriptor: &'tmp FnDescriptor<'ast>, +) -> Option<&'ast crate::impl_visitor::ReturnArg<'ast>> { + fn_descriptor.output_arg.as_ref().and_then(|output_arg| { + if let Some(receiver) = &fn_descriptor.receiver { + if receiver.name() == output_arg.name() { + return None; + } + } + + Some(output_arg) + }) +} + +pub fn gen_arg_ffi_to_src(arg: &impl crate::impl_visitor::Arg, is_output: bool) -> TokenStream { + let (arg_name, src_type) = (arg.name(), arg.src_type_resolved()); + + if is_output { + let mut stmt = quote! { + let mut store = (); + let #arg_name: #src_type = iroha_ffi::TryFromReprC::try_from_repr_c(#arg_name, &mut store)?; + }; + + if let Type::Reference(ref_type) = &src_type { + let elem = &ref_type.elem; + + stmt.extend(if ref_type.mutability.is_some() { + quote! { + // NOTE: Type having `type TryFromReprC::Store = ()` will never reference + // local context, i.e. it's lifetime can be attached to that of the wrapping fn + unsafe { &mut *(#arg_name as *mut #elem) } + } + } else { + quote! { + unsafe { &*(#arg_name as *const #elem) } + } + }); + } + + return stmt; + } + + quote! { + let mut store = core::default::Default::default(); + let #arg_name: #src_type = iroha_ffi::TryFromReprC::try_from_repr_c(#arg_name, &mut store)?; + } +} + +pub fn gen_arg_src_to_ffi(arg: &impl crate::impl_visitor::Arg, is_output: bool) -> TokenStream { + let (arg_name, src_type) = (arg.name(), arg.src_type()); + + let mut resolve_impl_trait = None; + if let Type::ImplTrait(type_) = &src_type { + for bound in &type_.bounds { + if let syn::TypeParamBound::Trait(trait_) = bound { + let trait_ = trait_.path.segments.last().expect_or_abort("Defined"); + + if trait_.ident == "IntoIterator" || trait_.ident == "ExactSizeIterator" { + resolve_impl_trait = Some(quote! { + let #arg_name: Vec<_> = #arg_name.into_iter().collect(); + }); + } else if trait_.ident == "Into" { + resolve_impl_trait = Some(quote! { + let #arg_name = #arg_name.into(); + }); + } + } + } + } + + let ffi_conversion = quote! { + #resolve_impl_trait + let #arg_name = iroha_ffi::IntoFfi::into_ffi(#arg_name); + }; + + if is_output { + if unwrap_result_type(src_type).is_some() { + return quote! { + let #arg_name = if let Ok(ok) = #arg_name { + iroha_ffi::IntoFfi::into_ffi(ok) + } else { + // TODO: Implement error handling (https://github.com/hyperledger/iroha/issues/2252) + return Err(FfiResult::ExecutionFail); + }; + }; + } + + return ffi_conversion; + } + + if let Type::Reference(ref_type) = &src_type { + if ref_type.mutability.is_some() { + return ffi_conversion; + } + } + + quote! { + #ffi_conversion + // NOTE: `AsReprCRef` prevents ownerhip transfer over FFI + let #arg_name = iroha_ffi::AsReprCRef::as_ref(&#arg_name); + } +} + +fn gen_output_assignment_stmts(fn_descriptor: &FnDescriptor) -> TokenStream { + if let Some(output_arg) = &fn_descriptor.output_arg { + if let Some(receiver) = &fn_descriptor.receiver { + let arg_name = receiver.name(); + let src_type = receiver.src_type(); + + if matches!(src_type, Type::Path(_)) { + return quote! { + if __out_ptr.is_null() { + return Err(iroha_ffi::FfiResult::ArgIsNull); + } + + __out_ptr.write(#arg_name); + }; + } + } + + let (arg_name, arg_type) = (output_arg.name(), output_arg.ffi_type_resolved()); + let output_arg_conversion = gen_arg_src_to_ffi(output_arg, true); + + return quote! { + #output_arg_conversion + iroha_ffi::OutPtrOf::<#arg_type>::write(__out_ptr, #arg_name)?; + }; + } + + quote! {} +} + +pub fn gen_ffi_fn_input_arg(arg: &impl crate::impl_visitor::Arg) -> TokenStream { + let arg_name = arg.name(); + let arg_type = arg.ffi_type_resolved(); + + quote! { #arg_name: #arg_type } +} + +pub fn gen_ffi_fn_out_ptr_arg(arg: &impl crate::impl_visitor::Arg) -> TokenStream { + let arg_name = arg.name(); + let arg_type = arg.ffi_type_resolved(); + + quote! { #arg_name: <#arg_type as iroha_ffi::Output>::OutPtr } +} diff --git a/ffi/derive/src/impl_visitor.rs b/ffi/derive/src/impl_visitor.rs index 348b08f36bc..c0e83bb3b43 100644 --- a/ffi/derive/src/impl_visitor.rs +++ b/ffi/derive/src/impl_visitor.rs @@ -1,18 +1,138 @@ -#![allow(clippy::unimplemented)] - +use derive_more::Constructor; use proc_macro2::Span; use proc_macro_error::{abort, OptionExt}; -use syn::{ - parse_quote, visit::Visit, visit_mut::VisitMut, Ident, PathArguments::AngleBracketed, Type, -}; +use syn::{parse_quote, visit::Visit, visit_mut::VisitMut, Ident, Type}; + +pub trait Arg { + fn name(&self) -> &Ident; + fn src_type(&self) -> &Type; + fn src_type_resolved(&self) -> Type; + fn ffi_type_resolved(&self) -> Type; +} + +#[derive(Constructor)] +pub struct Receiver<'ast> { + self_ty: &'ast syn::Path, + name: Ident, + type_: Type, +} -use crate::get_ident; +pub struct InputArg<'ast> { + self_ty: &'ast syn::Path, + name: &'ast Ident, + type_: &'ast Type, +} + +pub struct ReturnArg<'ast> { + self_ty: &'ast syn::Path, + name: Ident, + type_: &'ast Type, +} pub struct ImplDescriptor<'ast> { + /// Associated types in the impl block + pub associated_types: Vec<(&'ast Ident, &'ast Type)>, /// Functions in the impl block pub fns: Vec>, } +impl<'ast> InputArg<'ast> { + pub fn new(self_ty: &'ast syn::Path, name: &'ast Ident, type_: &'ast Type) -> Self { + Self { + self_ty, + name, + type_, + } + } +} + +impl<'ast> ReturnArg<'ast> { + pub fn new(self_ty: &'ast syn::Path, name: Ident, type_: &'ast Type) -> Self { + Self { + self_ty, + name, + type_, + } + } +} + +impl Arg for Receiver<'_> { + fn name(&self) -> &Ident { + &self.name + } + fn src_type(&self) -> &Type { + &self.type_ + } + fn src_type_resolved(&self) -> Type { + resolve_src_type(self.self_ty, self.type_.clone()) + } + fn ffi_type_resolved(&self) -> Type { + resolve_ffi_type(self.self_ty, self.type_.clone(), false) + } +} + +impl Arg for InputArg<'_> { + fn name(&self) -> &Ident { + self.name + } + fn src_type(&self) -> &Type { + self.type_ + } + fn src_type_resolved(&self) -> Type { + resolve_src_type(self.self_ty, self.type_.clone()) + } + fn ffi_type_resolved(&self) -> Type { + resolve_ffi_type(self.self_ty, self.type_.clone(), false) + } +} + +impl Arg for ReturnArg<'_> { + fn name(&self) -> &Ident { + &self.name + } + fn src_type(&self) -> &Type { + self.type_ + } + fn src_type_resolved(&self) -> Type { + resolve_src_type(self.self_ty, self.type_.clone()) + } + fn ffi_type_resolved(&self) -> Type { + resolve_ffi_type(self.self_ty, self.type_.clone(), true) + } +} + +fn resolve_src_type(self_ty: &syn::Path, mut arg_type: Type) -> Type { + SelfResolver::new(self_ty).visit_type_mut(&mut arg_type); + ImplTraitResolver.visit_type_mut(&mut arg_type); + + arg_type +} + +fn resolve_ffi_type(self_ty: &syn::Path, mut arg_type: Type, is_output: bool) -> Type { + SelfResolver::new(self_ty).visit_type_mut(&mut arg_type); + ImplTraitResolver.visit_type_mut(&mut arg_type); + + if is_output { + if let Some(result_type) = unwrap_result_type(&arg_type) { + return parse_quote! {<#result_type as iroha_ffi::IntoFfi>::Target}; + } + + return parse_quote! {<#arg_type as iroha_ffi::IntoFfi>::Target}; + } + + if let Type::Reference(ref_type) = &arg_type { + let elem = &ref_type.elem; + + return if ref_type.mutability.is_some() { + parse_quote! {<&'itm mut #elem as iroha_ffi::TryFromReprC<'itm>>::Source} + } else { + parse_quote! {<&'itm #elem as iroha_ffi::TryFromReprC<'itm>>::Source} + }; + } + + parse_quote! {<#arg_type as iroha_ffi::TryFromReprC<'itm>>::Source} +} + pub struct FnDescriptor<'ast> { /// Resolved type of the `Self` type pub self_ty: &'ast syn::Path, @@ -22,24 +142,17 @@ pub struct FnDescriptor<'ast> { /// Name of the method in the original implementation pub method_name: &'ast Ident, /// Receiver argument, i.e. `self` - pub receiver: Option, + pub receiver: Option>, /// Input fn arguments - pub input_args: Vec, + pub input_args: Vec>, /// Output fn argument - pub output_arg: Option, -} - -#[derive(Debug)] -pub struct FnArgDescriptor { - /// Name of the argument in an FFI function - pub ffi_name: Ident, - /// Type of the argument in a method implementation - pub src_type: Type, - /// Type of the argument in an FFI function - pub ffi_type: Type, + pub output_arg: Option>, } struct ImplVisitor<'ast> { + trait_name: Option<&'ast syn::Path>, + /// Associated types in the impl block + associated_types: Vec<(&'ast Ident, &'ast Type)>, /// Resolved type of the `Self` type self_ty: Option<&'ast syn::Path>, /// Collection of FFI functions @@ -55,11 +168,11 @@ struct FnVisitor<'ast> { /// Name of the method in the original implementation method_name: Option<&'ast Ident>, /// Receiver argument, i.e. `self` - receiver: Option, + receiver: Option>, /// Input fn arguments - input_args: Vec, + input_args: Vec>, /// Output fn argument - output_arg: Option, + output_arg: Option>, /// Name of the argument being visited curr_arg_name: Option<&'ast Ident>, @@ -74,15 +187,18 @@ impl<'ast> ImplDescriptor<'ast> { } fn from_visitor(visitor: ImplVisitor<'ast>) -> Self { - Self { fns: visitor.fns } + Self { + fns: visitor.fns, + associated_types: visitor.associated_types, + } } } impl<'ast> FnDescriptor<'ast> { fn from_impl_method(self_ty: &'ast syn::Path, node: &'ast syn::ImplItemMethod) -> Self { let mut visitor = FnVisitor::new(self_ty); - visitor.visit_impl_item_method(node); + visitor.visit_impl_item_method(node); FnDescriptor::from_visitor(visitor) } @@ -105,6 +221,8 @@ impl<'ast> FnDescriptor<'ast> { impl<'ast> ImplVisitor<'ast> { const fn new() -> Self { Self { + trait_name: None, + associated_types: Vec::new(), self_ty: None, fns: vec![], } @@ -113,62 +231,16 @@ impl<'ast> ImplVisitor<'ast> { fn visit_self_type(&mut self, node: &'ast Type) { if let Type::Path(self_ty) = node { if self_ty.qself.is_some() { - abort!(self_ty, "Qualified types not supported as self type"); + abort!(self_ty, "Qualified types are not supported as self type"); } self.self_ty = Some(&self_ty.path); } else { - abort!(node, "Only nominal types supported as self type"); + abort!(node, "Only nominal types are supported as self type"); } } } -impl FnArgDescriptor { - /// Returns true if this argument is a shared slice reference - pub fn is_slice_ref(&self) -> bool { - match &self.src_type { - Type::Reference(type_) => { - return type_.mutability.is_none() && matches!(*type_.elem, Type::Slice(_)); - } - Type::ImplTrait(type_) => { - assert_eq!(type_.bounds.len(), 1); - - if let syn::TypeParamBound::Trait(trait_) = &type_.bounds[0] { - let trait_name = get_ident(&trait_.path); - return trait_name == "IntoIterator"; - } - } - _ => return false, - } - - false - } - - /// Returns true if this argument is a mutable slice reference - pub fn is_slice_ref_mut(&self) -> bool { - match &self.src_type { - Type::Reference(type_) => { - return type_.mutability.is_some() && matches!(*type_.elem, Type::Slice(_)); - } - Type::ImplTrait(type_) => { - assert_eq!(type_.bounds.len(), 1); - - if let syn::TypeParamBound::Trait(trait_) = &type_.bounds[0] { - let trait_name = get_ident(&trait_.path); - return trait_name == "ExactSizeIterator"; - } - } - _ => return false, - } - - false - } - - pub const fn is_ffi_ptr(&self) -> bool { - matches!(self.ffi_type, Type::Ptr(_)) - } -} - impl<'ast> FnVisitor<'ast> { pub const fn new(self_ty: &'ast syn::Path) -> Self { Self { @@ -184,47 +256,42 @@ impl<'ast> FnVisitor<'ast> { } } - fn gen_self_arg_name() -> Ident { - Ident::new("handle", Span::call_site()) - } - - fn add_input_arg(&mut self, src_type: Type, ffi_type: Type) { - let ffi_name = self.curr_arg_name.take().expect_or_abort("Defined").clone(); - - self.input_args.push(FnArgDescriptor { - ffi_name, - src_type, - ffi_type, - }); + fn add_input_arg(&mut self, src_type: &'ast Type) { + let arg_name = self.curr_arg_name.take().expect_or_abort("Defined"); + self.input_args + .push(InputArg::new(self.self_ty, arg_name, src_type)); } /// Produces name of the return type. Name of the self argument is used for dummy /// output type which is not present in the FFI function signature. Dummy type is /// used to signal that the self type passes through the method being transcribed - fn gen_output_arg_name(&self, output_ffi_type: &Type) -> Ident { - if let Some(receiver) = &self.receiver { - if &receiver.ffi_type == output_ffi_type { - return receiver.ffi_name.clone(); + fn gen_output_arg_name(&mut self, output_src_type: &Type) -> Ident { + if let Some(receiver) = &mut self.receiver { + let self_src_ty = &mut receiver.type_; + + if *self_src_ty == *output_src_type { + if matches!(self_src_ty, Type::Path(_)) { + // NOTE: `Self` is first consumed and then returned in the same method + let name = core::mem::replace(&mut receiver.name, parse_quote! {irrelevant}); + *receiver = Receiver::new(self.self_ty, name, parse_quote! {#self_src_ty}); + } + + return receiver.name.clone(); } } - Ident::new("output", Span::call_site()) + Ident::new("__output", Span::call_site()) } - fn add_output_arg(&mut self, src_type: Type, mut ffi_type: Type) { + fn add_output_arg(&mut self, src_type: &'ast Type) { assert!(self.curr_arg_name.is_none()); assert!(self.output_arg.is_none()); - let ffi_name = self.gen_output_arg_name(&ffi_type); - if !matches!(src_type, Type::ImplTrait(_)) { - ffi_type = parse_quote! { *mut #ffi_type }; - } - - self.output_arg = Some(FnArgDescriptor { - ffi_name, + self.output_arg = Some(ReturnArg::new( + self.self_ty, + self.gen_output_arg_name(src_type), src_type, - ffi_type, - }); + )); } fn visit_impl_item_method_attribute(&mut self, node: &'ast syn::Attribute) { @@ -254,24 +321,24 @@ impl<'ast> Visit<'ast> for ImplVisitor<'ast> { if node.unsafety.is_some() { // NOTE: Its's irrelevant } - if let Some(trait_) = &node.trait_ { - abort!(trait_.1, "Only inherent impls are supported"); - } + self.trait_name = node.trait_.as_ref().map(|trait_| &trait_.1); self.visit_self_type(&*node.self_ty); for it in &node.items { - self.visit_impl_item(it); - } - } - fn visit_impl_item(&mut self, node: &'ast syn::ImplItem) { - let self_ty = self.self_ty.expect_or_abort("Defined"); - - match node { - syn::ImplItem::Method(method) => { - self.fns - .push(FnDescriptor::from_impl_method(self_ty, method)); + match it { + syn::ImplItem::Method(method) => { + let self_ty = self.self_ty.expect_or_abort("Defined"); + self.fns + .push(FnDescriptor::from_impl_method(self_ty, method)) + } + syn::ImplItem::Type(type_) => { + self.associated_types.push((&type_.ident, &type_.ty)); + } + _ => abort!( + node, + "Only methods or types are supported inside impl blocks" + ), } - _ => abort!(node, "Only methods are supported inside impl blocks"), } } } @@ -320,42 +387,23 @@ impl<'ast> Visit<'ast> for FnVisitor<'ast> { } } - let self_type = self.self_ty; - let (src_type, ffi_type) = node.reference.as_ref().map_or_else( - || { - ( - syn::TypePath { - qself: None, - path: self_type.clone(), - } - .into(), - parse_quote! { *mut #self_type }, - ) - }, + let src_type: Type = node.reference.as_ref().map_or_else( + || parse_quote! {Self}, |it| { if it.1.is_some() { abort!(it.1, "Explicit lifetime not supported"); } if node.mutability.is_some() { - ( - parse_quote! { &mut #self_type }, - parse_quote! { *mut #self_type }, - ) + parse_quote! {&mut Self} } else { - ( - parse_quote! { & #self_type }, - parse_quote! { *const #self_type }, - ) + parse_quote! {&Self} } }, ); - self.receiver = Some(FnArgDescriptor { - ffi_name: Self::gen_self_arg_name(), - src_type, - ffi_type, - }); + let handle_name = Ident::new("__handle", Span::call_site()); + self.receiver = Some(Receiver::new(self.self_ty, handle_name, src_type)); } fn visit_pat_type(&mut self, node: &'ast syn::PatType) { @@ -369,10 +417,7 @@ impl<'ast> Visit<'ast> for FnVisitor<'ast> { abort!(node.pat, "Unsupported pattern in variable name binding"); } - self.add_input_arg( - *node.ty.clone(), - TypeVisitor::resolve_ffi_type(self.self_ty, *node.ty.clone()), - ); + self.add_input_arg(&*node.ty); } fn visit_pat_ident(&mut self, node: &'ast syn::PatIdent) { @@ -396,209 +441,19 @@ impl<'ast> Visit<'ast> for FnVisitor<'ast> { match node { syn::ReturnType::Default => {} syn::ReturnType::Type(_, src_type) => { - let mut ffi_type = TypeVisitor::resolve_ffi_type(self.self_ty, *src_type.clone()); - - // NOTE: Transcribe owned output types to *mut ptr - if let (Type::Path(src_ty), Type::Ptr(ffi_ty)) = (*src_type.clone(), &mut ffi_type) - { - let ffi_ptr_subty = &ffi_ty.elem; - - if get_ident(&src_ty.path) != "Option" { - *ffi_ty = parse_quote! { *mut #ffi_ptr_subty }; - } - } - - self.add_output_arg(*src_type.clone(), ffi_type); - } - } - - if let Some(receiver) = &self.receiver { - let self_src_type = &receiver.src_type; - - if matches!(self_src_type, Type::Path(_)) { - let output_arg = self.output_arg.as_ref(); - - if output_arg.map_or(true, |out_arg| receiver.ffi_name != out_arg.ffi_name) { - abort!(self_src_type, "Methods which consume self not supported"); - } - } - } - } -} - -struct TypeVisitor { - ffi_type: Option, -} -impl TypeVisitor { - fn resolve_ffi_type(self_ty: &syn::Path, mut src_type: Type) -> Type { - SelfResolver::new(self_ty).visit_type_mut(&mut src_type); - let mut visitor = Self { ffi_type: None }; - visitor.visit_type(&src_type); - visitor.ffi_type.expect_or_abort("Defined") - } - - fn visit_item_binding(&mut self, seg: &syn::PathSegment) { - let bindings = generic_arg_bindings(seg); - - if bindings.is_empty() { - abort!(seg, "Missing generic argument `Item`"); - } - if bindings[0].ident != "Item" { - abort!(seg, "Unknown binding"); - } - - self.visit_type(&bindings[0].ty); - } -} - -impl<'ast> Visit<'ast> for TypeVisitor { - fn visit_type_array(&mut self, _: &'ast syn::TypeArray) { - unimplemented!("Not needed as of yet") - } - fn visit_type_bare_fn(&mut self, _: &'ast syn::TypeBareFn) { - unimplemented!("Not needed as of yet") - } - fn visit_type_group(&mut self, _: &'ast syn::TypeGroup) { - unimplemented!("Not needed as of yet") - } - fn visit_type_impl_trait(&mut self, node: &'ast syn::TypeImplTrait) { - if node.bounds.len() > 1 { - abort!( - node.bounds, - "Only one trait is allowed for the `impl trait` argument" - ); - } - - if let syn::TypeParamBound::Trait(trait_) = &node.bounds[0] { - let last_seg = trait_.path.segments.last().expect_or_abort("Defined"); - - if trait_.lifetimes.is_some() { - abort!(last_seg, "Lifetime bound not supported in `impl Trait`"); - } - - match last_seg.ident.to_string().as_str() { - "IntoIterator" => { - self.visit_item_binding(last_seg); - - self.ffi_type = { - let ffi_subty = &self.ffi_type; - Some(parse_quote! { *const #ffi_subty }) - }; - } - "ExactSizeIterator" => { - self.visit_item_binding(last_seg); - - self.ffi_type = { - let ffi_subty = &self.ffi_type; - Some(parse_quote! { *mut #ffi_subty }) - }; - } - "Into" => { - self.visit_type(generic_arg_types(last_seg)[0]); - } - _ => abort!(trait_, "Unsupported `impl trait`"), + self.add_output_arg(&**src_type); } } } - fn visit_type_infer(&mut self, _: &'ast syn::TypeInfer) { - unreachable!("Infer type not possible in a declaration") - } - fn visit_type_macro(&mut self, _: &'ast syn::TypeMacro) { - unimplemented!("Not needed as of yet") - } - fn visit_type_never(&mut self, _: &'ast syn::TypeNever) { - unimplemented!("Not needed as of yet") - } - fn visit_type_param(&mut self, _: &'ast syn::TypeParam) { - unimplemented!("Not needed as of yet") - } - fn visit_type_param_bound(&mut self, _: &'ast syn::TypeParamBound) { - unimplemented!("Not needed as of yet") - } - fn visit_type_paren(&mut self, _: &'ast syn::TypeParen) { - unimplemented!("Not needed as of yet") - } - fn visit_type_path(&mut self, node: &'ast syn::TypePath) { - let last_seg = node.path.segments.last().expect_or_abort("Defined"); - - match last_seg.ident.to_string().as_str() { - "bool" => self.ffi_type = Some(parse_quote! { u8 }), - "u8" | "u16" => self.ffi_type = Some(parse_quote! { u32 }), - "i8" | "i16" => self.ffi_type = Some(parse_quote! { i32 }), - "u32" | "i32" | "u64" | "i64" | "f32" | "f64" => { - self.ffi_type = Some(node.clone().into()) - } - "Option" => { - let option_ty = generic_arg_types(last_seg)[0]; - - match option_ty { - Type::Reference(type_) => self.visit_type_reference(type_), - _ => abort!(option_ty, "Unsupported Option type"), - } - } - "Result" => { - let args = generic_arg_types(last_seg); - let (ok_type, _) = (args[0], args[1]); - - match ok_type { - Type::Path(type_) => self.visit_type_path(type_), - Type::Reference(type_) => self.visit_type_reference(type_), - _ => abort!(ok_type, "Unsupported Result::Ok type"), - } - } - _ => self.ffi_type = Some(parse_quote! { *const #node }), - } - } - fn visit_type_ptr(&mut self, node: &'ast syn::TypePtr) { - abort!(node, "Raw pointers not supported") - } - fn visit_type_reference(&mut self, node: &'ast syn::TypeReference) { - if let Some(li) = &node.lifetime { - abort!(li, "Explicit lifetime not supported in reference types"); - } - - if node.mutability.is_some() { - abort!(node, "Mutable references not supported"); - } - - self.visit_type(&*node.elem); - - // NOTE: Owned opaque pointers produce double indirection - let mut ffi_type = self.ffi_type.take().expect_or_abort("Defined"); - if let (Type::Path(_), Type::Ptr(ffi_ptr_ty)) = (&*node.elem, &ffi_type) { - ffi_type = *ffi_ptr_ty.elem.clone(); - } - - self.ffi_type = Some(parse_quote! { *const #ffi_type }); - } - - fn visit_type_slice(&mut self, _: &'ast syn::TypeSlice) { - unimplemented!("Not needed as of yet") - } - fn visit_type_trait_object(&mut self, _: &'ast syn::TypeTraitObject) { - unimplemented!("Not needed as of yet") - } - fn visit_type_tuple(&mut self, node: &'ast syn::TypeTuple) { - if node.elems.len() != 2 { - abort!(node, "Only tuple pairs supported as of yet"); - } - - self.visit_type(&node.elems[0]); - let key = self.ffi_type.take(); - self.visit_type(&node.elems[1]); - let val = self.ffi_type.take(); - - self.ffi_type = Some(parse_quote! { iroha_ffi::Pair<#key, #val> }); - } } -/// Visitor for path types which replaces all occurrences of `Self` with a fully qualified type -pub struct SelfResolver<'ast> { +/// Visitor replaces all occurrences of `Self` in a path type with a fully qualified type +struct SelfResolver<'ast> { self_ty: &'ast syn::Path, } impl<'ast> SelfResolver<'ast> { - pub const fn new(self_ty: &'ast syn::Path) -> Self { + fn new(self_ty: &'ast syn::Path) -> Self { Self { self_ty } } } @@ -624,34 +479,66 @@ impl VisitMut for SelfResolver<'_> { } } -pub fn generic_arg_types(seg: &syn::PathSegment) -> Vec<&Type> { - if let AngleBracketed(arguments) = &seg.arguments { - let mut args = vec![]; - - for arg in &arguments.args { - if let syn::GenericArgument::Type(ty) = &arg { - args.push(ty); +struct ImplTraitResolver; +impl VisitMut for ImplTraitResolver { + fn visit_type_mut(&mut self, node: &mut Type) { + let mut new_node = None; + + if let Type::ImplTrait(impl_trait) = node { + for bound in &impl_trait.bounds { + if let syn::TypeParamBound::Trait(trait_) = bound { + let trait_ = trait_.path.segments.last().expect_or_abort("Defined"); + + if trait_.ident == "IntoIterator" || trait_.ident == "ExactSizeIterator" { + if let syn::PathArguments::AngleBracketed(args) = &trait_.arguments { + for arg in &args.args { + if let syn::GenericArgument::Binding(binding) = arg { + if binding.ident == "Item" { + let mut ty = binding.ty.clone(); + ImplTraitResolver.visit_type_mut(&mut ty); + + new_node = Some(parse_quote! { + Vec<#ty> + }); + } + } + } + } + } else if trait_.ident == "Into" { + if let syn::PathArguments::AngleBracketed(args) = &trait_.arguments { + for arg in &args.args { + if let syn::GenericArgument::Type(type_) = arg { + new_node = Some(type_.clone()); + } + } + } + } + } } } - return args; - }; + if let Some(new_node) = new_node { + *node = new_node; + } + } +} - abort!(seg, "Type not found in the given path segment") +fn get_ident(path: &syn::Path) -> &Ident { + &path.segments.last().expect_or_abort("Defined").ident } -fn generic_arg_bindings(seg: &syn::PathSegment) -> Vec<&syn::Binding> { - if let AngleBracketed(arguments) = &seg.arguments { - let mut bindings = vec![]; +pub fn unwrap_result_type(node: &Type) -> Option<&Type> { + if let Type::Path(type_) = node { + let last_seg = type_.path.segments.last().expect_or_abort("Defined"); - for arg in &arguments.args { - if let syn::GenericArgument::Binding(binding) = arg { - bindings.push(binding); + if last_seg.ident == "Result" { + if let syn::PathArguments::AngleBracketed(args) = &last_seg.arguments { + if let syn::GenericArgument::Type(result_type) = &args.args[0] { + return Some(result_type); + } } } + } - return bindings; - }; - - abort!(seg, "Binding not found in the given path segment") + None } diff --git a/ffi/derive/src/lib.rs b/ffi/derive/src/lib.rs index dc75f86325f..4ba2b108e8d 100644 --- a/ffi/derive/src/lib.rs +++ b/ffi/derive/src/lib.rs @@ -1,20 +1,22 @@ #![allow(clippy::str_to_string, missing_docs)] -use bindgen::gen_ffi_fn; use derive::gen_fns_from_derives; +use export::gen_ffi_fn; use impl_visitor::ImplDescriptor; use proc_macro::TokenStream; -use proc_macro_error::{abort, OptionExt}; +use proc_macro2::TokenStream as TokenStream2; +use proc_macro_error::abort; use quote::quote; -use syn::{parse_macro_input, Item}; +use syn::{parse_macro_input, parse_quote, Attribute, Ident, Item}; -mod bindgen; mod derive; +mod export; mod impl_visitor; +/// Generate FFI functions #[proc_macro_attribute] #[proc_macro_error::proc_macro_error] -pub fn ffi_bindgen(_attr: TokenStream, item: TokenStream) -> TokenStream { +pub fn ffi_export(_attr: TokenStream, item: TokenStream) -> TokenStream { match parse_macro_input!(item) { Item::Impl(item) => { let impl_descriptor = ImplDescriptor::from_impl(&item); @@ -31,7 +33,7 @@ pub fn ffi_bindgen(_attr: TokenStream, item: TokenStream) -> TokenStream { abort!(item.vis, "Only public structs allowed in FFI"); } if !item.generics.params.is_empty() { - abort!(item.generics, "Generic structs not supported"); + abort!(item.generics, "Generics are not supported"); } let ffi_fns = gen_fns_from_derives(&item); @@ -47,6 +49,389 @@ pub fn ffi_bindgen(_attr: TokenStream, item: TokenStream) -> TokenStream { .into() } -fn get_ident(path: &syn::Path) -> &syn::Ident { - &path.segments.last().expect_or_abort("Defined").ident +/// Derive implementations of traits required to convert into an FFI compatible type +#[proc_macro_derive(IntoFfi)] +#[proc_macro_error::proc_macro_error] +pub fn into_ffi_derive(input: TokenStream) -> TokenStream { + let input = parse_macro_input!(input as syn::DeriveInput); + + if !matches!(input.vis, syn::Visibility::Public(_)) { + abort!(input.vis, "Only public items are supported"); + } + + if !input.generics.params.is_empty() { + abort!(input.generics, "Generics are not supported"); + } + + match input.data { + syn::Data::Struct(_) => derive_into_ffi_for_struct(&input.ident, &input.attrs), + syn::Data::Enum(item) => derive_into_ffi_for_enum(&input.ident, &item, &input.attrs), + syn::Data::Union(item) => abort!(item.union_token, "Unions are not supported"), + } + .into() +} + +/// Derive implementations of traits required to convert from an FFI compatible type +#[proc_macro_derive(TryFromFfi)] +#[proc_macro_error::proc_macro_error] +pub fn try_from_ffi_derive(input: TokenStream) -> TokenStream { + let input = parse_macro_input!(input as syn::DeriveInput); + + if !matches!(input.vis, syn::Visibility::Public(_)) { + abort!(input.vis, "Only public items supported"); + } + + if !input.generics.params.is_empty() { + abort!(input.generics, "Generics are not supported"); + } + + match input.data { + syn::Data::Struct(_) => derive_try_from_ffi_for_struct(&input.ident, &input.attrs), + syn::Data::Enum(item) => derive_try_from_ffi_for_enum(&input.ident, &item, &input.attrs), + syn::Data::Union(item) => abort!(item.union_token, "Unions are not supported"), + } + .into() +} + +fn derive_try_from_ffi_for_struct(name: &Ident, attrs: &[Attribute]) -> TokenStream2 { + let repr: Vec<_> = find_repr(attrs).collect(); + + if !is_repr(&repr, "C") { + return derive_try_from_ffi_for_opaque_item(name); + } + + derive_try_from_ffi_for_item(name) +} + +fn derive_into_ffi_for_struct(name: &Ident, attrs: &[Attribute]) -> TokenStream2 { + let repr: Vec<_> = find_repr(attrs).collect(); + + if !is_repr(&repr, "C") { + return derive_into_ffi_for_opaque_item(name); + } + + derive_into_ffi_for_item(name) +} + +fn derive_into_ffi_for_enum( + name: &Ident, + item: &syn::DataEnum, + attrs: &[Attribute], +) -> TokenStream2 { + let repr: Vec<_> = find_repr(attrs).collect(); + + let is_fieldless = !item + .variants + .iter() + .any(|variant| !matches!(variant.fields, syn::Fields::Unit)); + + // NOTE: Verifies that repr(Int) is defined + enum_size(name, &repr); + + if is_fieldless { + return gen_fieldless_enum_into_ffi(name, &repr); + } + if !is_repr(&repr, "C") { + return derive_into_ffi_for_opaque_item(name); + } + + derive_into_ffi_for_item(name) +} + +fn derive_try_from_ffi_for_enum( + name: &Ident, + item: &syn::DataEnum, + attrs: &[Attribute], +) -> TokenStream2 { + let repr: Vec<_> = find_repr(attrs).collect(); + + let is_fieldless = !item + .variants + .iter() + .any(|variant| !matches!(variant.fields, syn::Fields::Unit)); + + // NOTE: Verifies that repr(Int) is defined + enum_size(name, &repr); + + if is_fieldless { + return gen_fieldless_enum_try_from_ffi(name, item, &repr); + } + if !is_repr(&repr, "C") { + return derive_try_from_ffi_for_opaque_item(name); + } + + derive_try_from_ffi_for_item(name) +} + +fn is_repr(repr: &[syn::NestedMeta], name: &str) -> bool { + repr.iter().any(|meta| { + if let syn::NestedMeta::Meta(item) = meta { + match item { + syn::Meta::Path(ref path) => { + if path.is_ident(name) { + return true; + } + } + _ => abort!(item, "Unknown repr attribute"), + } + } + + false + }) +} + +fn find_repr(attrs: &[Attribute]) -> impl Iterator + '_ { + attrs + .iter() + .filter_map(|attr| { + if let Ok(syn::Meta::List(meta_list)) = attr.parse_meta() { + return meta_list.path.is_ident("repr").then(|| meta_list.nested); + } + + None + }) + .flatten() +} + +fn derive_into_ffi_for_opaque_item(name: &Ident) -> TokenStream2 { + quote! { + impl iroha_ffi::IntoFfi for #name { + type Target = *mut Self; + + fn into_ffi(self) -> Self::Target { + Box::into_raw(Box::new(self)) + } + } + + impl iroha_ffi::IntoFfi for &#name { + type Target = *const #name; + + fn into_ffi(self) -> Self::Target { + <*const _>::from(self) + } + } + + impl iroha_ffi::IntoFfi for &mut #name { + type Target = *mut #name; + + fn into_ffi(self) -> Self::Target { + <*mut _>::from(self) + } + } + + impl iroha_ffi::slice::IntoFfiSliceRef<'_> for #name { + type Target = iroha_ffi::owned::LocalSlice<*const #name>; + + fn into_ffi(source: &[Self]) -> Self::Target { + source.iter().map(IntoFfi::into_ffi).collect() + } + } + } +} + +fn derive_try_from_ffi_for_opaque_item(name: &Ident) -> TokenStream2 { + quote! { + impl<'itm> iroha_ffi::TryFromReprC<'itm> for #name { + type Source = *mut #name; + type Store = (); + + unsafe fn try_from_repr_c(source: Self::Source, _: &mut >::Store) -> Result { + if source.is_null() { + return Err(iroha_ffi::FfiResult::ArgIsNull); + } + + Ok(*Box::from_raw(source)) + } + } + impl<'itm> iroha_ffi::TryFromReprC<'itm> for &'itm #name { + type Source = *const #name; + type Store = (); + + unsafe fn try_from_repr_c(source: Self::Source, _: &mut >::Store) -> Result { + source.as_ref().ok_or(iroha_ffi::FfiResult::ArgIsNull) + } + } + impl<'itm> iroha_ffi::TryFromReprC<'itm> for &'itm mut #name { + type Source = *mut #name; + type Store = (); + + unsafe fn try_from_repr_c(source: Self::Source, _: &mut >::Store) -> Result { + source.as_mut().ok_or(iroha_ffi::FfiResult::ArgIsNull) + } + } + + impl<'itm> iroha_ffi::slice::TryFromReprCSliceRef<'itm> for #name { + type Source = iroha_ffi::slice::SliceRef<'itm, <&'itm Self as iroha_ffi::TryFromReprC<'itm>>::Source>; + type Store = Vec; + + unsafe fn try_from_repr_c(source: Self::Source, store: &'itm mut >::Store) -> Result<&'itm [Self], iroha_ffi::FfiResult> { + let source = source.into_rust().ok_or(iroha_ffi::FfiResult::ArgIsNull)?; + + for elem in source { + store.push(Clone::clone(iroha_ffi::TryFromReprC::try_from_repr_c(*elem, &mut ())?)); + } + + Ok(store) + } + } + } +} + +#[allow(clippy::restriction)] +fn derive_into_ffi_for_item(_: &Ident) -> TokenStream2 { + unimplemented!("https://github.com/hyperledger/iroha/issues/2510") +} + +#[allow(clippy::restriction)] +fn derive_try_from_ffi_for_item(_: &Ident) -> TokenStream2 { + unimplemented!("https://github.com/hyperledger/iroha/issues/2510") +} + +fn gen_fieldless_enum_into_ffi(enum_name: &Ident, repr: &[syn::NestedMeta]) -> TokenStream2 { + let ffi_type = enum_size(enum_name, repr); + + quote! { + impl iroha_ffi::IntoFfi for #enum_name { + type Target = #ffi_type; + + fn into_ffi(self) -> Self::Target { + self as #ffi_type + } + } + + impl iroha_ffi::IntoFfi for &#enum_name { + type Target = *const #ffi_type; + + fn into_ffi(self) -> Self::Target { + self as *const #enum_name as *const #ffi_type + } + } + + impl iroha_ffi::IntoFfi for &mut #enum_name { + type Target = *mut #ffi_type; + + fn into_ffi(self) -> Self::Target { + self as *mut #enum_name as *mut #ffi_type + } + } + } +} + +fn gen_fieldless_enum_try_from_ffi( + enum_name: &Ident, + enum_: &syn::DataEnum, + repr: &[syn::NestedMeta], +) -> TokenStream2 { + let variant_names: Vec<_> = enum_.variants.iter().map(|v| &v.ident).collect(); + let discriminant_values = variant_discriminants(enum_); + + let ffi_type = enum_size(enum_name, repr); + let (discriminants, discriminant_names) = + variant_names.iter().zip(discriminant_values.iter()).fold( + <(Vec<_>, Vec<_>)>::default(), + |mut acc, (variant_name, discriminant_value)| { + let discriminant_name = Ident::new( + &format!("{}__{}", enum_name, variant_name).to_uppercase(), + proc_macro2::Span::call_site(), + ); + + acc.0.push(quote! { + const #discriminant_name: #ffi_type = #discriminant_value; + }); + acc.1.push(discriminant_name); + + acc + }, + ); + + quote! { + impl<'itm> iroha_ffi::TryFromReprC<'itm> for #enum_name { + type Source = #ffi_type; + type Store = (); + + unsafe fn try_from_repr_c(source: Self::Source, _: &mut >::Store) -> Result { + #( #discriminants )* + + match source { + #( #discriminant_names => Ok(#enum_name::#variant_names), )* + _ => Err(iroha_ffi::FfiResult::TrapRepresentation), + } + } + } + impl<'itm> iroha_ffi::TryFromReprC<'itm> for &'itm #enum_name { + type Source = *const #ffi_type; + type Store = (); + + unsafe fn try_from_repr_c(source: Self::Source, _: &mut >::Store) -> Result { + #( #discriminants )* + + unsafe { match *source { + #( | #discriminant_names )* => Ok(&*(source as *const _ as *const _)), + _ => Err(iroha_ffi::FfiResult::TrapRepresentation), + }} + } + } + impl<'itm> iroha_ffi::TryFromReprC<'itm> for &'itm mut #enum_name { + type Source = *mut #ffi_type; + type Store = (); + + unsafe fn try_from_repr_c(source: Self::Source, _: &mut >::Store) -> Result { + #( #discriminants )* + + unsafe { match *source { + #( | #discriminant_names )* => Ok(&mut *(source as *mut _ as *mut _)), + _ => Err(iroha_ffi::FfiResult::TrapRepresentation), + }} + } + } + + impl<'itm> iroha_ffi::slice::TryFromReprCSliceRef<'itm> for #enum_name { + type Source = iroha_ffi::slice::SliceRef<'itm, Self>; + type Store = (); + + unsafe fn try_from_repr_c(source: Self::Source, _: &mut >::Store) -> Result<&'itm [Self], iroha_ffi::FfiResult> { + source.into_rust().ok_or(iroha_ffi::FfiResult::ArgIsNull) + } + } + impl<'slice> iroha_ffi::slice::TryFromReprCSliceMut<'slice> for #enum_name { + type Source = iroha_ffi::slice::SliceMut<'slice, #enum_name>; + type Store = (); + + unsafe fn try_from_repr_c(source: Self::Source, _: &mut ::Store) -> Result<&'slice mut [Self], iroha_ffi::FfiResult> { + source.into_rust().ok_or(iroha_ffi::FfiResult::ArgIsNull) + } + } + } +} + +fn variant_discriminants(enum_: &syn::DataEnum) -> Vec { + let mut curr_discriminant: syn::Expr = parse_quote! {0}; + + enum_.variants.iter().fold(Vec::new(), |mut acc, variant| { + let discriminant = variant.discriminant.as_ref().map_or_else( + || curr_discriminant.clone(), + |discriminant| discriminant.1.clone(), + ); + + acc.push(discriminant.clone()); + curr_discriminant = parse_quote! { + 1 + #discriminant + }; + + acc + }) +} + +fn enum_size(enum_name: &Ident, repr: &[syn::NestedMeta]) -> TokenStream2 { + if is_repr(repr, "u8") { + quote! {u8} + } else if is_repr(repr, "u16") { + quote! {u16} + } else if is_repr(repr, "u32") { + quote! {u32} + } else if is_repr(repr, "u64") { + quote! {u64} + } else { + abort!(enum_name, "Enum doesn't have a valid representation") + } } diff --git a/ffi/derive/tests/ui_fail/derive_skip_field.rs b/ffi/derive/tests/ui_fail/derive_skip_field.rs index a784441624c..0f23bc08b6b 100644 --- a/ffi/derive/tests/ui_fail/derive_skip_field.rs +++ b/ffi/derive/tests/ui_fail/derive_skip_field.rs @@ -1,30 +1,31 @@ +use core::mem::MaybeUninit; + use getset::{Getters, Setters}; -use iroha_ffi::ffi_bindgen; -use std::mem::MaybeUninit; +use iroha_ffi::{ffi_export, IntoFfi, TryFromFfi, TryFromReprC}; -#[ffi_bindgen] -#[derive(Setters, Getters)] +#[derive(Clone, Setters, Getters, IntoFfi, TryFromFfi)] +#[ffi_export] #[getset(get = "pub")] pub struct FfiStruct { #[getset(set = "pub")] - a: u32, + a: i32, #[getset(skip)] b: u32, } fn main() { - let s: *mut _ = &mut FfiStruct { a: 42, b: 32 }; + let s = FfiStruct { a: 42, b: 32 }; - let a = MaybeUninit::<*const u32>::uninit(); - let b = MaybeUninit::<*const u32>::uninit(); + let mut a = MaybeUninit::<*const i32>::uninit(); + let mut b = MaybeUninit::<*const u32>::uninit(); unsafe { - FfiStruct__a(s, a.as_mut_ptr()); - let a = &*a.assume_init(); - FfiStruct__set_a(s, a); + FfiStruct__a(IntoFfi::into_ffi(&s), a.as_mut_ptr()); + let a: &i32 = TryFromReprC::try_from_repr_c(a.assume_init(), &mut ()).unwrap(); + FfiStruct__set_a(IntoFfi::into_ffi(&mut s), IntoFfi::into_ffi(*a)); - FfiStruct__b(s, b.as_mut_ptr()); - let b = &*b.assume_init(); - FfiStruct__set_b(s, b); + FfiStruct__b(IntoFfi::into_ffi(&s), b.as_mut_ptr()); + let b: &u32 = TryFromReprC::try_from_repr_c(b.assume_init(), &mut ()).unwrap(); + FfiStruct__set_b(IntoFfi::into_ffi(&mut s), IntoFfi::into_ffi(*b)); } } diff --git a/ffi/derive/tests/ui_fail/derive_skip_field.stderr b/ffi/derive/tests/ui_fail/derive_skip_field.stderr index 59bed6baada..831f7fea8ec 100644 --- a/ffi/derive/tests/ui_fail/derive_skip_field.stderr +++ b/ffi/derive/tests/ui_fail/derive_skip_field.stderr @@ -1,17 +1,17 @@ error[E0425]: cannot find function, tuple struct or tuple variant `FfiStruct__b` in this scope - --> tests/ui_fail/derive_skip_field.rs:26:9 + --> tests/ui_fail/derive_skip_field.rs:27:9 | -5 | #[ffi_bindgen] - | -------------- similarly named function `FfiStruct__a` defined here +7 | #[ffi_export] + | ------------- similarly named function `FfiStruct__a` defined here ... -26 | FfiStruct__b(s, b.as_mut_ptr()); +27 | FfiStruct__b(IntoFfi::into_ffi(&s), b.as_mut_ptr()); | ^^^^^^^^^^^^ help: a function with a similar name exists: `FfiStruct__a` error[E0425]: cannot find function, tuple struct or tuple variant `FfiStruct__set_b` in this scope - --> tests/ui_fail/derive_skip_field.rs:28:9 + --> tests/ui_fail/derive_skip_field.rs:29:9 | -5 | #[ffi_bindgen] - | -------------- similarly named function `FfiStruct__set_a` defined here +7 | #[ffi_export] + | ------------- similarly named function `FfiStruct__set_a` defined here ... -28 | FfiStruct__set_b(s, b); +29 | FfiStruct__set_b(IntoFfi::into_ffi(&mut s), IntoFfi::into_ffi(*b)); | ^^^^^^^^^^^^^^^^ help: a function with a similar name exists: `FfiStruct__set_a` diff --git a/ffi/derive/tests/ui_fail/derive_skip_struct.rs b/ffi/derive/tests/ui_fail/derive_skip_struct.rs index 35359241594..8bc5d63c55b 100644 --- a/ffi/derive/tests/ui_fail/derive_skip_struct.rs +++ b/ffi/derive/tests/ui_fail/derive_skip_struct.rs @@ -1,9 +1,10 @@ +use core::mem::MaybeUninit; + use getset::{MutGetters, Setters}; -use iroha_ffi::ffi_bindgen; -use std::mem::MaybeUninit; +use iroha_ffi::{ffi_export, IntoFfi, TryFromFfi, TryFromReprC}; -#[ffi_bindgen] -#[derive(Setters, MutGetters)] +#[derive(Clone, Setters, MutGetters, IntoFfi, TryFromFfi)] +#[ffi_export] #[getset(skip)] pub struct FfiStruct { #[getset(set = "pub", get_mut = "pub")] @@ -12,18 +13,18 @@ pub struct FfiStruct { } fn main() { - let s: *mut _ = &mut FfiStruct { a: 42, b: 32 }; + let s = FfiStruct { a: 42, b: 32 }; - let a = MaybeUninit::<*mut u32>::uninit(); - let b = MaybeUninit::<*const i32>::uninit(); + let mut a = MaybeUninit::<*mut u32>::uninit(); + let mut b = MaybeUninit::<*mut i32>::uninit(); unsafe { - FfiStruct__a_mut(s, a.as_mut_ptr()); - let a = &mut *a.assume_init(); - FfiStruct__set_a(s, a); + FfiStruct__a_mut(IntoFfi::into_ffi(&mut s), a.as_mut_ptr()); + let a: &mut u32 = TryFromReprC::try_from_repr_c(a.assume_init(), &mut ()).unwrap(); + FfiStruct__set_a(IntoFfi::into_ffi(&mut s), IntoFfi::into_ffi(*a)); - FfiStruct__b_mut(s, b.as_mut_ptr()); - let b = &*b.assume_init(); - FfiStruct__set_b(s, b); + FfiStruct__b_mut(IntoFfi::into_ffi(&s), b.as_mut_ptr()); + let b: &mut i32 = TryFromReprC::try_from_repr_c(b.assume_init(), &mut ()).unwrap(); + FfiStruct__set_b(IntoFfi::into_ffi(&mut s), IntoFfi::into_ffi(*b)); } } diff --git a/ffi/derive/tests/ui_fail/derive_skip_struct.stderr b/ffi/derive/tests/ui_fail/derive_skip_struct.stderr index 386deedc6a3..17d681acc32 100644 --- a/ffi/derive/tests/ui_fail/derive_skip_struct.stderr +++ b/ffi/derive/tests/ui_fail/derive_skip_struct.stderr @@ -1,17 +1,17 @@ error[E0425]: cannot find function, tuple struct or tuple variant `FfiStruct__b_mut` in this scope - --> tests/ui_fail/derive_skip_struct.rs:25:9 + --> tests/ui_fail/derive_skip_struct.rs:26:9 | -5 | #[ffi_bindgen] - | -------------- similarly named function `FfiStruct__a_mut` defined here +7 | #[ffi_export] + | ------------- similarly named function `FfiStruct__a_mut` defined here ... -25 | FfiStruct__b_mut(s, b.as_mut_ptr()); +26 | FfiStruct__b_mut(IntoFfi::into_ffi(&s), b.as_mut_ptr()); | ^^^^^^^^^^^^^^^^ help: a function with a similar name exists: `FfiStruct__a_mut` error[E0425]: cannot find function, tuple struct or tuple variant `FfiStruct__set_b` in this scope - --> tests/ui_fail/derive_skip_struct.rs:27:9 + --> tests/ui_fail/derive_skip_struct.rs:28:9 | -5 | #[ffi_bindgen] - | -------------- similarly named function `FfiStruct__set_a` defined here +7 | #[ffi_export] + | ------------- similarly named function `FfiStruct__set_a` defined here ... -27 | FfiStruct__set_b(s, b); +28 | FfiStruct__set_b(IntoFfi::into_ffi(&mut s), IntoFfi::into_ffi(*b)); | ^^^^^^^^^^^^^^^^ help: a function with a similar name exists: `FfiStruct__set_a` diff --git a/ffi/derive/tests/ui_fail/mutable_input_arg.rs b/ffi/derive/tests/ui_fail/mutable_input_arg.rs deleted file mode 100644 index 7207ad6aa35..00000000000 --- a/ffi/derive/tests/ui_fail/mutable_input_arg.rs +++ /dev/null @@ -1,30 +0,0 @@ -//! Test which assures that methods taking mutable references as input arguments are not -//! allowed in the FFI. This limitation can be lifted in the future, and consequently -//! this test nullified, if the need for mutable input arguments feature arises - -use iroha_ffi::{ffi_bindgen, FfiResult}; -use std::mem::MaybeUninit; - -struct FfiStruct { - a: u32, -} - -#[ffi_bindgen] -impl FfiStruct { - /// From mutable input arg - pub fn from_mutable_input_arg(a: &mut u32) -> Self { - let output = Self { a: a.clone() }; - *a = 42; - output - } -} - -fn main() -> Result<(), ()> { - let s: MaybeUninit<*mut FfiStruct> = MaybeUninit::uninit(); - - if FfiResult::Ok != FfiStruct__from_mutable_input_arg(s.as_mut_ptr()) { - return Err(()); - } - - Ok(()) -} diff --git a/ffi/derive/tests/ui_fail/mutable_input_arg.stderr b/ffi/derive/tests/ui_fail/mutable_input_arg.stderr deleted file mode 100644 index eb4cdede3d9..00000000000 --- a/ffi/derive/tests/ui_fail/mutable_input_arg.stderr +++ /dev/null @@ -1,11 +0,0 @@ -error: Mutable references not supported - --> tests/ui_fail/mutable_input_arg.rs:15:38 - | -15 | pub fn from_mutable_input_arg(a: &mut u32) -> Self { - | ^^^^^^^^ - -error[E0425]: cannot find function, tuple struct or tuple variant `FfiStruct__from_mutable_input_arg` in this scope - --> tests/ui_fail/mutable_input_arg.rs:25:25 - | -25 | if FfiResult::Ok != FfiStruct__from_mutable_input_arg(s.as_mut_ptr()) { - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ not found in this scope diff --git a/ffi/derive/tests/ui_fail/self_consuming_method.rs b/ffi/derive/tests/ui_fail/self_consuming_method.rs deleted file mode 100644 index 612ba3bff91..00000000000 --- a/ffi/derive/tests/ui_fail/self_consuming_method.rs +++ /dev/null @@ -1,36 +0,0 @@ -//! Test which assures that methods consuming self are not allowed in FFI. If the need arises, -//! this limitation can be lifted in the future, and consequently this test nullified - -use iroha_ffi::{ffi_bindgen, FfiResult}; -use std::mem::MaybeUninit; - -struct FfiStructBuilder; -struct FfiStruct; - -#[ffi_bindgen] -impl FfiStructBuilder { - /// New - pub fn new() -> Self { - Self - } - - /// Build - pub fn build(self) -> FfiStruct { - FfiStruct - } -} - -fn main() -> Result<(), ()> { - let s_builder: MaybeUninit<*mut FfiStructBuilder> = MaybeUninit::uninit(); - if FfiResult::Ok != FfiStructBuilder__new(s_builder.as_mut_ptr()) { - return Err(()); - } - - let s_builder = unsafe { s_builder.assume_init() }; - let s: MaybeUninit<*mut FfiStruct> = MaybeUninit::uninit(); - if FfiResult::Ok != FfiStructBuilder__build(s_builder, s.as_mut_ptr()) { - return Err(()); - } - - Ok(()) -} diff --git a/ffi/derive/tests/ui_fail/self_consuming_method.stderr b/ffi/derive/tests/ui_fail/self_consuming_method.stderr deleted file mode 100644 index 3c06aa77699..00000000000 --- a/ffi/derive/tests/ui_fail/self_consuming_method.stderr +++ /dev/null @@ -1,23 +0,0 @@ -error: Methods which consume self not supported - --> tests/ui_fail/self_consuming_method.rs:11:6 - | -11 | impl FfiStructBuilder { - | ^^^^^^^^^^^^^^^^ - -error[E0425]: cannot find function, tuple struct or tuple variant `FfiStructBuilder__new` in this scope - --> tests/ui_fail/self_consuming_method.rs:25:25 - | -7 | struct FfiStructBuilder; - | ------------------------ similarly named unit struct `FfiStructBuilder` defined here -... -25 | if FfiResult::Ok != FfiStructBuilder__new(s_builder.as_mut_ptr()) { - | ^^^^^^^^^^^^^^^^^^^^^ help: a unit struct with a similar name exists: `FfiStructBuilder` - -error[E0425]: cannot find function, tuple struct or tuple variant `FfiStructBuilder__build` in this scope - --> tests/ui_fail/self_consuming_method.rs:31:25 - | -7 | struct FfiStructBuilder; - | ------------------------ similarly named unit struct `FfiStructBuilder` defined here -... -31 | if FfiResult::Ok != FfiStructBuilder__build(s_builder, s.as_mut_ptr()) { - | ^^^^^^^^^^^^^^^^^^^^^^^ help: a unit struct with a similar name exists: `FfiStructBuilder` diff --git a/ffi/derive/tests/ui_pass/shared_fns.rs b/ffi/derive/tests/ui_pass/shared_fns.rs index b8aa1b7cf4a..3bf51db7b76 100644 --- a/ffi/derive/tests/ui_pass/shared_fns.rs +++ b/ffi/derive/tests/ui_pass/shared_fns.rs @@ -1,13 +1,15 @@ use std::{cmp::Ordering, mem::MaybeUninit}; -use iroha_ffi::{ffi_bindgen, gen_ffi_impl, handles}; +use iroha_ffi::{ + ffi_export, gen_ffi_impl, handles, AsReprCRef, Handle, IntoFfi, TryFromFfi, TryFromReprC, +}; -#[derive(Clone, PartialEq, Eq, PartialOrd, Ord)] +#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, IntoFfi, TryFromFfi)] pub struct FfiStruct1 { name: String, } -#[derive(Clone, PartialEq, Eq, PartialOrd, Ord)] +#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, IntoFfi, TryFromFfi)] pub struct FfiStruct2 { name: String, } @@ -18,7 +20,7 @@ gen_ffi_impl! {Clone: FfiStruct1, FfiStruct2} gen_ffi_impl! {Eq: FfiStruct1, FfiStruct2} gen_ffi_impl! {Ord: FfiStruct1, FfiStruct2} -#[ffi_bindgen] +#[ffi_export] impl FfiStruct1 { /// New pub fn new(name: String) -> Self { @@ -29,42 +31,51 @@ impl FfiStruct1 { fn main() { let name = String::from("X"); - let ffi_struct1 = unsafe { - let mut ffi_struct: MaybeUninit<*mut FfiStruct1> = MaybeUninit::uninit(); - FfiStruct1__new(&name, ffi_struct.as_mut_ptr()); - ffi_struct.assume_init() + let ffi_struct1: FfiStruct1 = unsafe { + let mut ffi_struct = MaybeUninit::<*mut FfiStruct1>::uninit(); + let name = IntoFfi::into_ffi(name); + FfiStruct1__new(name.as_ref(), ffi_struct.as_mut_ptr()); + TryFromReprC::try_from_repr_c(ffi_struct.assume_init(), &mut ()).unwrap() }; unsafe { - let cloned = { + let cloned: FfiStruct1 = { let mut cloned: MaybeUninit<*mut FfiStruct1> = MaybeUninit::uninit(); __clone( FfiStruct1::ID, - ffi_struct1.cast(), + (&ffi_struct1).into_ffi().cast(), cloned.as_mut_ptr().cast(), ); - cloned.assume_init() + TryFromReprC::try_from_repr_c(cloned.assume_init(), &mut ()).unwrap() }; - let mut is_equal: MaybeUninit = MaybeUninit::uninit(); + let mut is_equal: MaybeUninit = MaybeUninit::uninit(); __eq( FfiStruct1::ID, - ffi_struct1.cast(), - cloned.cast(), + (&ffi_struct1).into_ffi().cast(), + (&cloned).into_ffi().cast(), is_equal.as_mut_ptr(), ); + assert_eq!( + true, + TryFromReprC::try_from_repr_c(is_equal.assume_init(), &mut ()).unwrap() + ); - let mut ordering: MaybeUninit = MaybeUninit::uninit(); + let mut ordering: MaybeUninit = MaybeUninit::uninit(); __ord( FfiStruct1::ID, - ffi_struct1.cast(), - cloned.cast(), + (&ffi_struct1).into_ffi().cast(), + (&cloned).into_ffi().cast(), ordering.as_mut_ptr(), ); + assert_eq!( + Ordering::Equal, + TryFromReprC::try_from_repr_c(ordering.assume_init(), &mut ()).unwrap() + ); - __drop(FfiStruct1::ID, ffi_struct1.cast()); - __drop(FfiStruct1::ID, cloned.cast()); + __drop(FfiStruct1::ID, ffi_struct1.into_ffi().cast()); + __drop(FfiStruct1::ID, cloned.into_ffi().cast()); } } diff --git a/ffi/derive/tests/ui_pass/valid.rs b/ffi/derive/tests/ui_pass/valid.rs index 2b0f7fa2e05..cc2edc351b4 100644 --- a/ffi/derive/tests/ui_pass/valid.rs +++ b/ffi/derive/tests/ui_pass/valid.rs @@ -1,15 +1,18 @@ use std::{collections::BTreeMap, mem::MaybeUninit}; use getset::Getters; -use iroha_ffi::{ffi_bindgen, gen_ffi_impl, handles, Pair}; +use iroha_ffi::{ + ffi_export, gen_ffi_impl, handles, slice::OutBoxedSlice, AsReprCRef, Handle, IntoFfi, + TryFromFfi, TryFromReprC, +}; -#[derive(Clone, PartialEq, Eq, PartialOrd, Ord)] +#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, IntoFfi, TryFromFfi)] pub struct Name(&'static str); -#[derive(Clone)] +#[derive(Clone, IntoFfi, TryFromFfi)] pub struct Value(&'static str); -#[ffi_bindgen] -#[derive(Getters)] +#[ffi_export] +#[derive(Clone, Getters, IntoFfi, TryFromFfi)] #[getset(get = "pub")] pub struct FfiStruct { name: Name, @@ -20,7 +23,7 @@ pub struct FfiStruct { handles! {0, FfiStruct} gen_ffi_impl! {Drop: FfiStruct} -#[ffi_bindgen] +#[ffi_export] impl FfiStruct { /// New pub fn new(name: Name) -> Self { @@ -50,32 +53,32 @@ impl FfiStruct { fn main() { let name = Name("X"); - let ffi_struct = unsafe { - let mut ffi_struct: MaybeUninit<*mut FfiStruct> = MaybeUninit::uninit(); - FfiStruct__new(&name, ffi_struct.as_mut_ptr()); - ffi_struct.assume_init() + let mut ffi_struct: FfiStruct = unsafe { + let mut ffi_struct = MaybeUninit::<*mut FfiStruct>::uninit(); + let name = IntoFfi::into_ffi(name.clone()); + FfiStruct__new(name, ffi_struct.as_mut_ptr()); + TryFromReprC::try_from_repr_c(ffi_struct.assume_init(), &mut ()).unwrap() }; - let in_params: Vec> = vec![(Name("Nomen"), Value("Omen"))] - .iter() - .map(|(key, val)| Pair(key as *const _, val as *const _)) - .collect(); - + let in_params = vec![(Name("Nomen"), Value("Omen"))].into_ffi(); let mut param: MaybeUninit<*const Value> = MaybeUninit::uninit(); - let mut out_params: Vec> = Vec::new(); - let mut params_len: MaybeUninit = MaybeUninit::uninit(); + let mut out_params_data = Vec::with_capacity(2); + let mut data_len = MaybeUninit::::uninit(); + + let out_params = + OutBoxedSlice::from_uninit_slice(Some(&mut out_params_data[..]), &mut data_len); unsafe { - FfiStruct__with_params(ffi_struct, in_params.as_ptr(), in_params.len()); - FfiStruct__get_param(ffi_struct, &name, param.as_mut_ptr()); + let name = IntoFfi::into_ffi(name.clone()); + + FfiStruct__with_params(IntoFfi::into_ffi(&mut ffi_struct), in_params.as_ref()); + FfiStruct__get_param(IntoFfi::into_ffi(&ffi_struct), name, param.as_mut_ptr()); + FfiStruct__params(IntoFfi::into_ffi(&ffi_struct), out_params); - FfiStruct__params( - ffi_struct, - out_params.as_mut_ptr(), - out_params.capacity(), - params_len.as_mut_ptr(), - ); + let _param: Option<&Value> = + TryFromReprC::try_from_repr_c(param.assume_init(), &mut ()).unwrap(); + out_params_data.set_len(data_len.assume_init() as usize); - __drop(FfiStruct::ID, ffi_struct.cast()); + __drop(FfiStruct::ID, ffi_struct.into_ffi().cast()); } } diff --git a/ffi/src/handle.rs b/ffi/src/handle.rs new file mode 100644 index 00000000000..385cacce50b --- /dev/null +++ b/ffi/src/handle.rs @@ -0,0 +1,174 @@ +//! Logic related to opaque pointer handles and functions that are common to multiple handle types + +/// Type of the handle id +pub type Id = u8; + +/// Implement [`$crate::Handle`] for given types with the given initial handle id. +#[macro_export] +macro_rules! handles { + ( $id:expr, $ty:ty $(, $other:ty)* $(,)? ) => { + unsafe impl $crate::Handle for $ty { + const ID: $crate::handle::Id = $id; + } + + $crate::handles! {$id + 1, $( $other, )*} + }; + ( $id:expr, $(,)? ) => {}; +} + +/// Generate FFI equivalent implementation of the requested trait method (e.g. Clone, Eq, Ord) +#[macro_export] +macro_rules! gen_ffi_impl { + (@catch_unwind $block:block ) => { + match std::panic::catch_unwind(|| $block) { + Ok(res) => match res { + Ok(()) => $crate::FfiResult::Ok, + Err(err) => err.into(), + }, + Err(_) => { + // TODO: Implement error handling (https://github.com/hyperledger/iroha/issues/2252) + $crate::FfiResult::UnrecoverableError + }, + } + }; + ( $vis:vis Clone: $( $other:ty ),+ $(,)? ) => { + /// FFI function equivalent of [`Clone::clone`] + /// + /// # Safety + /// + /// All of the given pointers must be valid and the given handle id must match the expected + /// pointer type + #[no_mangle] + $vis unsafe extern "C" fn __clone( + handle_id: $crate::handle::Id, + handle_ptr: *const core::ffi::c_void, + output_ptr: *mut *mut core::ffi::c_void + ) -> $crate::FfiResult { + gen_ffi_impl!(@catch_unwind { + use core::borrow::Borrow; + + // False positive - doesn't compile otherwise + #[allow(clippy::let_unit_value)] + match handle_id { + $( <$other as $crate::Handle>::ID => { + let handle_ptr = handle_ptr.cast::<$other>(); + let mut store = Default::default(); + let handle_ref: &$other = $crate::TryFromReprC::try_from_repr_c(handle_ptr, &mut store)?; + + let new_handle = Clone::clone(handle_ref); + let new_handle_ptr = $crate::IntoFfi::into_ffi(new_handle).into(); + output_ptr.cast::<*mut $other>().write(new_handle_ptr); + } )+ + // TODO: Implement error handling (https://github.com/hyperledger/iroha/issues/2252) + _ => return Err($crate::FfiResult::UnknownHandle), + } + + Ok(()) + }) + } + }; + ( $vis:vis Eq: $( $other:ty ),+ $(,)? ) => { + /// FFI function equivalent of [`Eq::eq`] + /// + /// # Safety + /// + /// All of the given pointers must be valid and the given handle id must match the expected + /// pointer type + #[no_mangle] + $vis unsafe extern "C" fn __eq( + handle_id: $crate::handle::Id, + left_handle_ptr: *const core::ffi::c_void, + right_handle_ptr: *const core::ffi::c_void, + output_ptr: *mut u8, + ) -> $crate::FfiResult { + gen_ffi_impl!(@catch_unwind { + use core::borrow::Borrow; + + // False positive - doesn't compile otherwise + #[allow(clippy::let_unit_value)] + match handle_id { + $( <$other as $crate::Handle>::ID => { + let (lhandle_ptr, rhandle_ptr) = (left_handle_ptr.cast::<$other>(), right_handle_ptr.cast::<$other>()); + + let mut lhandle_store = Default::default(); + let mut rhandle_store = Default::default(); + + let lhandle: &$other = $crate::TryFromReprC::try_from_repr_c(lhandle_ptr, &mut lhandle_store)?; + let rhandle: &$other = $crate::TryFromReprC::try_from_repr_c(rhandle_ptr, &mut rhandle_store)?; + + output_ptr.write($crate::IntoFfi::into_ffi(lhandle == rhandle).into()); + } )+ + // TODO: Implement error handling (https://github.com/hyperledger/iroha/issues/2252) + _ => return Err($crate::FfiResult::UnknownHandle), + } + + Ok(()) + }) + } + }; + ( $vis:vis Ord: $( $other:ty ),+ $(,)? ) => { + /// FFI function equivalent of [`Ord::ord`] + /// + /// # Safety + /// + /// All of the given pointers must be valid and the given handle id must match the expected + /// pointer type + #[no_mangle] + $vis unsafe extern "C" fn __ord( + handle_id: $crate::handle::Id, + left_handle_ptr: *const core::ffi::c_void, + right_handle_ptr: *const core::ffi::c_void, + output_ptr: *mut i8, + ) -> $crate::FfiResult { + gen_ffi_impl!(@catch_unwind { + use core::borrow::Borrow; + + // False positive - doesn't compile otherwise + #[allow(clippy::let_unit_value)] + match handle_id { + $( <$other as $crate::Handle>::ID => { + let (lhandle_ptr, rhandle_ptr) = (left_handle_ptr.cast::<$other>(), right_handle_ptr.cast::<$other>()); + + let mut lhandle_store = Default::default(); + let mut rhandle_store = Default::default(); + + let lhandle: &$other = $crate::TryFromReprC::try_from_repr_c(lhandle_ptr, &mut lhandle_store)?; + let rhandle: &$other = $crate::TryFromReprC::try_from_repr_c(rhandle_ptr, &mut rhandle_store)?; + + output_ptr.write($crate::IntoFfi::into_ffi(lhandle.cmp(rhandle)).into()); + } )+ + // TODO: Implement error handling (https://github.com/hyperledger/iroha/issues/2252) + _ => return Err($crate::FfiResult::UnknownHandle), + } + + Ok(()) + }) + } + }; + ( $vis:vis Drop: $( $other:ty ),+ $(,)? ) => { + /// FFI function equivalent of [`Drop::drop`] + /// + /// # Safety + /// + /// All of the given pointers must be valid and the given handle id must match the expected + /// pointer type + #[no_mangle] + $vis unsafe extern "C" fn __drop( + handle_id: $crate::handle::Id, + handle_ptr: *mut core::ffi::c_void, + ) -> $crate::FfiResult { + gen_ffi_impl!(@catch_unwind { + match handle_id { + $( <$other as $crate::Handle>::ID => { + let handle_ptr = handle_ptr.cast::<$other>(); + let handle: $other = $crate::TryFromReprC::try_from_repr_c(handle_ptr, &mut ())?; + } )+ + // TODO: Implement error handling (https://github.com/hyperledger/iroha/issues/2252) + _ => return Err($crate::FfiResult::UnknownHandle), + } + + Ok(()) + }) + } + }; +} diff --git a/ffi/src/lib.rs b/ffi/src/lib.rs index ea4f4495ad6..a4085781f9d 100644 --- a/ffi/src/lib.rs +++ b/ffi/src/lib.rs @@ -1,173 +1,329 @@ +#![allow(unsafe_code)] + //! Structures, macros related to FFI and generation of FFI bindings. +//! [Non-robust types](https://anssi-fr.github.io/rust-guide/07_ffi.html#non-robust-types-references-function-pointers-enums) +//! are strictly avoided in the FFI API pub use iroha_ffi_derive::*; +use owned::Local; + +pub mod handle; +pub mod option; +pub mod owned; +mod primitives; +pub mod slice; + +/// Represents the handle in an FFI context +/// +/// # Safety +/// +/// If two structures implement the same id, it may result in a void pointer being casted to the wrong type +pub unsafe trait Handle { + /// Unique identifier of the handle. Most commonly, it is + /// used to facilitate generic monomorphization over FFI + const ID: handle::Id; +} + +/// Robust type that conforms to C ABI and can be safely shared across FFI boundaries. This does +/// not guarantee the ABI compatibility of the referent for pointers. These pointers are opaque +/// +/// # Safety +/// +/// Type implementing the trait must be a robust type with a guaranteed C ABI. Care must be taken +/// not to dereference pointers whose referents don't implement `ReprC`; they are considered opaque +pub unsafe trait ReprC: Sized {} + +/// Used to do a cheap reference-to-[`ReprC`]-reference conversion +pub trait AsReprCRef<'itm> { + /// Robust C ABI compliant representation of &[`Self`] + type Target: ReprC + 'itm; + + /// Convert from &[`Self`] into [`Self::Target`]. + fn as_ref(&'itm self) -> Self::Target; +} + +/// Conversion from a type that implements [`ReprC`]. +pub trait TryFromReprC<'itm>: Sized + 'itm { + /// Robust C ABI compliant representation of [`Self`] + type Source: ReprC + Copy; -// NOTE: Using `u32` to be compatible with WebAssembly. -// Otherwise `u8` should be sufficient -/// Type of the handle id -pub type HandleId = u32; + /// Type into which state can be stored during conversion. Useful for returning + /// non-owning types but performing some conversion which requires allocation. + /// Serves similar purpose as does context in a closure + type Store: Default; -/// FFI compatible tuple with 2 elements -#[repr(C)] -#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] -pub struct Pair(pub K, pub V); + /// Convert from [`Self::Source`] into [`Self`]. + /// Transferring ownership over FFI is not permitted, except for opaque pointer types + /// + /// # Errors + /// + /// * [`FfiResult::ArgIsNull`] - given pointer is null + /// * [`FfiResult::UnknownHandle`] - given id doesn't identify any known handle + /// * [`FfiResult::TrapRepresentation`] - given value contains trap representation + /// + /// # Safety + /// + /// All conversions from a pointer must ensure pointer validity beforehand + unsafe fn try_from_repr_c( + source: Self::Source, + store: &'itm mut Self::Store, + ) -> Result; +} + +/// Conversion into a type that can be converted to an FFI-compatible [`ReprC`] type +/// Except for opaque pointer types, ownership transfer over FFI is not permitted +pub trait IntoFfi: Sized { + /// The resulting type after conversion + type Target: ReprC; + + /// Convert from [`Self`] into [`Self::Target`] + fn into_ffi(self) -> Self::Target; +} + +/// Type that can be returned from an FFI function as an out-pointer function argument +pub trait OutPtrOf: ReprC { + /// Try to write `T` into [`Self`] out-pointer and return whether or not it was successful + /// + /// # Errors + /// + /// * [`FfiResult::ArgIsNull`] - if any of the out-pointers in [`Self`] is set to null + /// + /// # Safety + /// + /// All conversions from a pointer must ensure pointer validity beforehand + unsafe fn write(self, source: T) -> Result<(), FfiResult>; +} + +/// Type that can be returned from an FFI function via out-pointer function argument +pub trait Output: Sized { + /// Corresponding type of out-pointer + type OutPtr: OutPtrOf; +} /// Result of execution of an FFI function #[derive(Debug, Clone, Copy, PartialEq, Eq)] -// NOTE: Enum is `repr(i32)` becasuse WebAssembly supports only -// u32/i32, u64/i64 natively. Otherwise, `repr(i8)` would suffice -#[repr(i32)] +#[repr(i8)] pub enum FfiResult { - /// Handle id doesn't identify any known handles - UnknownHandle = -4_i32, - /// Executing the wrapped method on handle returned error - ExecutionFail = -3_i32, - /// Raw pointer input argument to FFI function was null - ArgIsNull = -2_i32, - /// Given bytes don't comprise a valid UTF8 string - Utf8Error = -1_i32, - /// FFI function executed successfully - Ok = 0_i32, -} - -/// Implement `Handle` for given types with first argument as the initial handle id. -#[macro_export] -macro_rules! handles { - ( $id:expr, $ty:ty $(, $other:ty)* $(,)? ) => { - impl Handle for $ty { - const ID: $crate::HandleId = $id; + /// The input argument provided to FFI function has a trap representation. + TrapRepresentation = -6, + /// FFI function execution panicked. + UnrecoverableError = -5, + /// Provided handle id doesn't match any known handles. + UnknownHandle = -4, + /// FFI function failed during the execution of the wrapped method on the provided handle. + ExecutionFail = -3, + /// The input argument provided to FFI function is a null pointer. + ArgIsNull = -2, + /// The input argument provided to FFI function is not a valid UTF-8 string. + Utf8Error = -1, + /// FFI function executed successfully. + Ok = 0, +} + +unsafe impl ReprC for *const T {} +unsafe impl ReprC for *mut T {} + +impl<'itm, T: ReprC + Copy + 'itm> AsReprCRef<'itm> for T +where + T: IntoFfi, +{ + type Target = Self; + + fn as_ref(&self) -> Self::Target { + *self + } +} +impl<'itm, T: 'itm> AsReprCRef<'itm> for *const T { + type Target = Self; + + fn as_ref(&self) -> Self::Target { + *self + } +} + +impl<'itm, T: ReprC> TryFromReprC<'itm> for &'itm T { + type Source = *const T; + type Store = (); + + unsafe fn try_from_repr_c(source: Self::Source, _: &mut ()) -> Result { + source.as_ref().ok_or(FfiResult::ArgIsNull) + } +} +impl<'itm, T: ReprC> TryFromReprC<'itm> for &'itm mut T { + type Source = *mut T; + type Store = (); + + unsafe fn try_from_repr_c(source: Self::Source, _: &mut ()) -> Result { + source.as_mut().ok_or(FfiResult::ArgIsNull) + } +} + +impl IntoFfi for &T +where + T: IntoFfi, +{ + type Target = *const T; + + fn into_ffi(self) -> Self::Target { + Self::Target::from(self) + } +} +impl IntoFfi for &mut T +where + T: IntoFfi, +{ + type Target = *mut T; + + fn into_ffi(self) -> Self::Target { + Self::Target::from(self) + } +} + +impl OutPtrOf<*mut T> for *mut *mut T { + unsafe fn write(self, source: *mut T) -> Result<(), FfiResult> { + if self.is_null() { + return Err(FfiResult::ArgIsNull); } - $crate::handles! {$id + 1, $( $other, )*} - }; - ( $id:expr, $(,)? ) => { - /// Represents handle in an FFI context - pub trait Handle { - /// Unique identifier of the handle. Most commonly, it is - /// used to facilitate generic monomorphization over FFI - const ID: $crate::HandleId; + self.write(source); + Ok(()) + } +} +impl OutPtrOf<*const T> for *mut *const T { + unsafe fn write(self, source: *const T) -> Result<(), FfiResult> { + if self.is_null() { + return Err(FfiResult::ArgIsNull); } - }; + + self.write(source); + Ok(()) + } } +impl OutPtrOf for *mut T +where + T: IntoFfi, +{ + unsafe fn write(self, source: T) -> Result<(), FfiResult> { + if self.is_null() { + return Err(FfiResult::ArgIsNull); + } -/// Generate FFI equivalent implementation of the requested trait method (e.g. Clone, Eq, Ord) -#[macro_export] -macro_rules! gen_ffi_impl { - (@null_check_stmts $( $ptr:ident ),+ ) => { - $( if $ptr.is_null() { - return $crate::FfiResult::ArgIsNull; - } )+ - }; - ( Clone: $( $other:ty ),+ $(,)? ) => { - /// FFI function equivalent of [`Clone::clone`] - /// - /// # Safety - /// - /// All of the given pointers must be valid and the given handle id must match the expected - /// pointer type - #[no_mangle] - pub unsafe extern "C" fn __clone( - handle_id: $crate::HandleId, - handle_ptr: *const core::ffi::c_void, - output_ptr: *mut *mut core::ffi::c_void - ) -> $crate::FfiResult { - gen_ffi_impl!{@null_check_stmts handle_ptr, output_ptr} - - match handle_id { - $( <$other as Handle>::ID => { - let handle = &*handle_ptr.cast::<$other>(); - - let new_handle = Box::new(Clone::clone(handle)); - let new_handle = Box::into_raw(new_handle); - - output_ptr.write(new_handle.cast()); - } )+ - _ => return $crate::FfiResult::UnknownHandle, - } + self.write(source); + Ok(()) + } +} +impl OutPtrOf> for *mut T { + unsafe fn write(self, source: Local) -> Result<(), FfiResult> { + if self.is_null() { + return Err(FfiResult::ArgIsNull); + } + + self.write(source.0); + Ok(()) + } +} + +impl Output for *mut T { + type OutPtr = *mut *mut T; +} +impl Output for *const T { + type OutPtr = *mut *const T; +} +impl Output for T +where + T: IntoFfi, + *mut Self: OutPtrOf, +{ + type OutPtr = *mut Self; +} + +macro_rules! impl_tuple { + ( ($( $ty:ident ),+ $(,)?) -> $ffi_ty:ident ) => { + /// FFI-compatible tuple with n elements + #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] + #[repr(C)] + pub struct $ffi_ty<$($ty),+>($(pub $ty),+); - $crate::FfiResult::Ok + #[allow(non_snake_case)] + impl<$($ty),+> From<($( $ty, )*)> for $ffi_ty<$($ty),+> { + fn from(source: ($( $ty, )*)) -> Self { + let ($($ty,)+) = source; + Self($( $ty ),*) + } } - }; - ( Eq: $( $other:ty ),+ $(,)? ) => { - /// FFI function equivalent of [`Eq::eq`] - /// - /// # Safety - /// - /// All of the given pointers must be valid and the given handle id must match the expected - /// pointer type - #[no_mangle] - pub unsafe extern "C" fn __eq( - handle_id: $crate::HandleId, - left_handle_ptr: *const core::ffi::c_void, - right_handle_ptr: *const core::ffi::c_void, - output_ptr: *mut bool, - ) -> $crate::FfiResult { - gen_ffi_impl!{@null_check_stmts left_handle_ptr, right_handle_ptr, output_ptr} - - match handle_id { - $( <$other as Handle>::ID => { - let left_handle = &*left_handle_ptr.cast::<$other>(); - let right_handle = &*right_handle_ptr.cast::<$other>(); - - output_ptr.write(left_handle == right_handle); - } )+ - _ => return $crate::FfiResult::UnknownHandle, + + unsafe impl<$($ty: ReprC),+> ReprC for $ffi_ty<$($ty),+> {} + + impl<'itm, $($ty: ReprC + 'itm),+> AsReprCRef<'itm> for $ffi_ty<$($ty),+> { + type Target = *const Self; + + fn as_ref(&self) -> Self::Target { + <*const Self>::from(self) } + } + + impl<'itm, $($ty: TryFromReprC<'itm>),+> TryFromReprC<'itm> for ($($ty,)+) { + type Source = $ffi_ty<$($ty::Source),+>; + type Store = ($( $ty::Store, )*); - $crate::FfiResult::Ok + #[allow(non_snake_case)] + unsafe fn try_from_repr_c(source: Self::Source, store: &'itm mut Self::Store) -> Result { + impl_tuple! {@decl_priv_mod $($ty),+} + + let $ffi_ty($($ty,)+) = source; + let store: private::Store<$($ty),+> = store.into(); + Ok(($( <$ty as TryFromReprC>::try_from_repr_c($ty, store.$ty)?, )+)) + } } - }; - ( Ord: $( $other:ty ),+ $(,)? ) => { - /// FFI function equivalent of [`Ord::ord`] - /// - /// # Safety - /// - /// All of the given pointers must be valid and the given handle id must match the expected - /// pointer type - #[no_mangle] - pub unsafe extern "C" fn __ord( - handle_id: $crate::HandleId, - left_handle_ptr: *const core::ffi::c_void, - right_handle_ptr: *const core::ffi::c_void, - output_ptr: *mut core::cmp::Ordering, - ) -> $crate::FfiResult { - gen_ffi_impl!{@null_check_stmts left_handle_ptr, right_handle_ptr, output_ptr} - - match handle_id { - $( <$other as Handle>::ID => { - let left_handle = &*left_handle_ptr.cast::<$other>(); - let right_handle = &*right_handle_ptr.cast::<$other>(); - - output_ptr.write(left_handle.cmp(right_handle)); - } )+ - _ => return $crate::FfiResult::UnknownHandle, + + impl<$($ty: IntoFfi),+> IntoFfi for ($( $ty, )+) { + type Target = $ffi_ty<$($ty::Target),+>; + + #[allow(non_snake_case)] + fn into_ffi(self) -> Self::Target { + let ($($ty,)+) = self; + $ffi_ty($( <$ty as IntoFfi>::into_ffi($ty),)+) } + } + // TODO: With specialization it should be possible to avoid clone + impl<$($ty: IntoFfi + Clone),+> IntoFfi for &($( $ty, )+) { + type Target = Local<$ffi_ty<$($ty::Target,)+>>; - $crate::FfiResult::Ok + #[allow(non_snake_case)] + fn into_ffi(self) -> Self::Target { + let ($($ty,)+) = Clone::clone(self); + Local::new($ffi_ty($( <$ty as IntoFfi>::into_ffi($ty),)+)) + } } }; - ( Drop: $( $other:ty ),+ $(,)? ) => { - /// FFI function equivalent of [`Drop::drop`] - /// - /// # Safety - /// - /// All of the given pointers must be valid and the given handle id must match the expected - /// pointer type - #[no_mangle] - pub unsafe extern "C" fn __drop( - handle_id: $crate::HandleId, - handle_ptr: *mut core::ffi::c_void, - ) -> $crate::FfiResult { - gen_ffi_impl!{@null_check_stmts handle_ptr} - - match handle_id { - $( <$other as Handle>::ID => { - Box::from_raw(handle_ptr.cast::<$other>()); - } )+ - _ => return $crate::FfiResult::UnknownHandle, + + // NOTE: This is a trick to index tuples + ( @decl_priv_mod $( $ty:ident ),+ $(,)? ) => { + mod private { + #[allow(non_snake_case)] + pub struct Store<'itm, $($ty: super::TryFromReprC<'itm>),+>{ + $(pub $ty: &'itm mut $ty::Store),+ } - $crate::FfiResult::Ok + #[allow(non_snake_case)] + impl<'store, 'tup, $($ty: super::TryFromReprC<'tup>),+> From<&'tup mut ($($ty::Store,)+)> for Store<'tup, $($ty),+> { + fn from(($($ty,)+): &'tup mut ($($ty::Store,)+)) -> Self { + Self {$($ty,)+} + } + } } }; } + +impl_tuple! {(A) -> FfiTuple1} +impl_tuple! {(A, B) -> FfiTuple2} +impl_tuple! {(A, B, C) -> FfiTuple3} +impl_tuple! {(A, B, C, D) -> FfiTuple4} +impl_tuple! {(A, B, C, D, E) -> FfiTuple5} +impl_tuple! {(A, B, C, D, E, F) -> FfiTuple6} +impl_tuple! {(A, B, C, D, E, F, G) -> FfiTuple7} +impl_tuple! {(A, B, C, D, E, F, G, H) -> FfiTuple8} +impl_tuple! {(A, B, C, D, E, F, G, H, I) -> FfiTuple9} +impl_tuple! {(A, B, C, D, E, F, G, H, I, J) -> FfiTuple10} +impl_tuple! {(A, B, C, D, E, F, G, H, I, J, K) -> FfiTuple11} +impl_tuple! {(A, B, C, D, E, F, G, H, I, J, K, L) -> FfiTuple12} diff --git a/ffi/src/option.rs b/ffi/src/option.rs new file mode 100644 index 00000000000..75cc5061623 --- /dev/null +++ b/ffi/src/option.rs @@ -0,0 +1,142 @@ +//! Logic related to the conversion of [`Option`] to and from FFI-compatible representation + +use crate::{ + owned::LocalSlice, + slice::{SliceMut, SliceRef}, + FfiResult, IntoFfi, ReprC, TryFromReprC, +}; + +/// Type with an FFI-compatible representation that supports [`Option::None`] values +pub trait Nullable: ReprC { + /// Return null value + fn null() -> Self; + /// Return `true` if the value is null, otherwise `false` + fn is_null(&self) -> bool; +} + +/// Trait that facilitates the implementation of [`IntoFfi`] for [`Option`] of foreign types +pub trait IntoFfiOption: Sized { + /// [`Option`] equivalent of [`IntoFfi::Target`] + type Target: ReprC; + + /// Convert from [`Option`] into [`Self::Target`] + fn into_ffi(source: Option) -> Self::Target; +} + +/// Trait that facilitates the implementation of [`TryFromReprC`] for [`Option`] of foreign types +pub trait TryFromReprCOption<'itm>: Sized + 'itm { + /// Type that can be converted from a [`ReprC`] type that was sent over FFI + type Source: ReprC + Copy; + + /// Type into which state can be stored during conversion. Useful for returning + /// non-owning types but performing some conversion which requires allocation. + /// Serves similar purpose as does context in a closure + type Store: Default; + + /// Perform the fallible conversion + /// + /// # Errors + /// + /// * [`FfiResult::ArgIsNull`] - given pointer is null + /// * [`FfiResult::UnknownHandle`] - given id doesn't identify any known handle + /// * [`FfiResult::TrapRepresentation`] - given value contains trap representation + /// + /// # Safety + /// + /// All conversions from a pointer must ensure pointer validity beforehand + unsafe fn try_from_repr_c( + source: Self::Source, + store: &'itm mut Self::Store, + ) -> Result, FfiResult>; +} + +impl Nullable for *const T { + fn null() -> Self { + core::ptr::null() + } + fn is_null(&self) -> bool { + (*self).is_null() + } +} +impl Nullable for *mut T { + fn null() -> Self { + core::ptr::null_mut() + } + fn is_null(&self) -> bool { + (*self).is_null() + } +} +impl Nullable for SliceRef<'_, T> { + fn null() -> Self { + SliceRef::null() + } + fn is_null(&self) -> bool { + self.is_null() + } +} +impl Nullable for SliceMut<'_, T> { + fn null() -> Self { + SliceMut::null() + } + fn is_null(&self) -> bool { + self.is_null() + } +} +impl Nullable for LocalSlice { + fn null() -> Self { + LocalSlice::null() + } + fn is_null(&self) -> bool { + self.is_null() + } +} + +impl<'itm, T: TryFromReprCOption<'itm>> TryFromReprC<'itm> for Option { + type Source = T::Source; + type Store = T::Store; + + unsafe fn try_from_repr_c( + source: Self::Source, + store: &'itm mut Self::Store, + ) -> Result { + TryFromReprCOption::try_from_repr_c(source, store) + } +} + +impl<'itm, T: TryFromReprC<'itm>> TryFromReprCOption<'itm> for T +where + T::Source: Nullable, +{ + type Source = T::Source; + type Store = T::Store; + + unsafe fn try_from_repr_c( + source: Self::Source, + store: &'itm mut Self::Store, + ) -> Result, FfiResult> { + if source.is_null() { + return Ok(None); + } + + Ok(Some(TryFromReprC::try_from_repr_c(source, store)?)) + } +} + +impl IntoFfi for Option { + type Target = T::Target; + + fn into_ffi(self) -> Self::Target { + IntoFfiOption::into_ffi(self) + } +} + +impl IntoFfiOption for T +where + T::Target: Nullable, +{ + type Target = T::Target; + + fn into_ffi(source: Option) -> Self::Target { + source.map_or_else(T::Target::null, IntoFfi::into_ffi) + } +} diff --git a/ffi/src/owned.rs b/ffi/src/owned.rs new file mode 100644 index 00000000000..c5fdfa7152b --- /dev/null +++ b/ffi/src/owned.rs @@ -0,0 +1,207 @@ +//! Logic related to the conversion of structures with ownership. Ownership is never transferred +//! across FFI. This means that contents of these structures are copied into provided containers + +use crate::{ + slice::{OutBoxedSlice, SliceRef}, + AsReprCRef, FfiResult, IntoFfi, Output, ReprC, TryFromReprC, +}; + +/// Wrapper around `T` that is local to the conversion site. This structure carries +/// ownership and care must be taken not to let it transfer ownership into an FFI function +// NOTE: It's not possible to mutably reference local context +#[derive(Clone)] +#[repr(transparent)] +pub struct Local(pub(crate) T); + +/// Wrapper around [`Option>`] that is local to the conversion site. This structure +/// carries ownership and care must be taken not to let it transfer ownership into FFI function +// NOTE: It's not possible to mutably reference local context +#[derive(Debug)] +#[repr(C)] +pub struct LocalSlice(*const T, usize, core::marker::PhantomData); + +unsafe impl ReprC for Local {} +unsafe impl ReprC for LocalSlice {} + +impl Drop for LocalSlice { + fn drop(&mut self) { + if self.is_null() { + return; + } + + // SAFETY: Data pointer must either be a null pointer or point to a valid memory + unsafe { Box::from_raw(core::slice::from_raw_parts_mut(self.0 as *mut T, self.1)) }; + } +} + +impl FromIterator for LocalSlice { + fn from_iter>(iter: T) -> Self { + let items: Box<[_]> = iter.into_iter().collect(); + let mut items = core::mem::ManuallyDrop::new(items); + + Self(items.as_mut_ptr(), items.len(), core::marker::PhantomData) + } +} + +impl<'itm, T: ReprC + 'itm> AsReprCRef<'itm> for Local { + type Target = *const T; + + fn as_ref(&self) -> Self::Target { + &(self.0) + } +} +impl<'slice, T: ReprC + 'slice> AsReprCRef<'slice> for LocalSlice { + type Target = SliceRef<'slice, T>; + + fn as_ref(&self) -> Self::Target { + if self.is_null() { + return SliceRef::null(); + } + + SliceRef::from_raw_parts(self.0, self.1) + } +} + +impl Output for Local { + type OutPtr = *mut T; +} +impl<'data, T: ReprC + Copy> Output for LocalSlice { + type OutPtr = OutBoxedSlice; +} + +impl Local { + /// Create [`Self`] from the given element + pub fn new(elem: T) -> Self { + Self(elem) + } +} + +impl LocalSlice { + /// Convert [`Self`] into a boxed slice + /// + /// # Safety + /// + /// Data pointer must point to a valid memory + pub unsafe fn into_rust(self) -> Option> { + if self.is_null() { + return None; + } + + let slice = core::mem::ManuallyDrop::new(self); + Some(Box::from_raw(core::slice::from_raw_parts_mut( + slice.0 as *mut T, + slice.1, + ))) + } + + /// Convert [`Self`] into a shared slice + /// + /// # Safety + /// + /// Data pointer must point to a valid memory + pub unsafe fn as_rust<'slice>(&self) -> Option<&'slice [T]> { + if self.is_null() { + return None; + } + + Some(core::slice::from_raw_parts(self.0, self.1)) + } + + /// Construct `None` variant + pub fn null() -> Self { + // TODO: len could be uninitialized + Self(core::ptr::null_mut(), 0, core::marker::PhantomData) + } + + /// Return true if type is null, otherwhise false + pub fn is_null(&self) -> bool { + self.0.is_null() + } +} + +impl IntoFfi for Vec +where + T::Target: ReprC, +{ + type Target = LocalSlice; + + fn into_ffi(self) -> Self::Target { + self.into_iter().map(IntoFfi::into_ffi).collect() + } +} + +impl<'itm, T: TryFromReprC<'itm>> TryFromReprC<'itm> for Vec { + type Source = SliceRef<'itm, T::Source>; + type Store = Vec; + + unsafe fn try_from_repr_c( + source: Self::Source, + store: &'itm mut Self::Store, + ) -> Result { + let prev_store_len = store.len(); + let slice = source.into_rust().ok_or(FfiResult::ArgIsNull)?; + store.extend(core::iter::repeat_with(Default::default).take(slice.len())); + + let mut substore = &mut store[prev_store_len..]; + let mut res = Vec::with_capacity(slice.len()); + + let mut i = 0; + while let Some((first, rest)) = substore.split_first_mut() { + res.push(>::try_from_repr_c(slice[i], first)?); + substore = rest; + i += 1; + } + + Ok(res) + } +} + +impl<'itm> TryFromReprC<'itm> for String { + type Source = as TryFromReprC<'itm>>::Source; + type Store = (); + + unsafe fn try_from_repr_c( + source: Self::Source, + _: &mut Self::Store, + ) -> Result { + String::from_utf8(source.into_rust().ok_or(FfiResult::ArgIsNull)?.to_owned()) + .map_err(|_e| FfiResult::Utf8Error) + } +} +impl<'itm> TryFromReprC<'itm> for &'itm str { + type Source = <&'itm [u8] as TryFromReprC<'itm>>::Source; + type Store = (); + + unsafe fn try_from_repr_c( + source: Self::Source, + _: &mut Self::Store, + ) -> Result { + core::str::from_utf8(source.into_rust().ok_or(FfiResult::ArgIsNull)?) + .map_err(|_e| FfiResult::Utf8Error) + } +} + +impl IntoFfi for String { + type Target = as IntoFfi>::Target; + + fn into_ffi(self) -> Self::Target { + self.into_bytes().into_ffi() + } +} + +impl<'slice> IntoFfi for &'slice str { + type Target = <&'slice [u8] as IntoFfi>::Target; + + fn into_ffi(self) -> Self::Target { + self.as_bytes().into_ffi() + } +} + +unsafe impl ReprC for [T; N] {} +impl IntoFfi for [T; N] { + type Target = LocalSlice; + + fn into_ffi(self) -> Self::Target { + self.into_iter().map(IntoFfi::into_ffi).collect() + } +} diff --git a/ffi/src/primitives.rs b/ffi/src/primitives.rs new file mode 100644 index 00000000000..a6129731a57 --- /dev/null +++ b/ffi/src/primitives.rs @@ -0,0 +1,228 @@ +#![allow(trivial_casts)] + +use crate::{ + slice::{ + IntoFfiSliceMut, IntoFfiSliceRef, SliceMut, SliceRef, TryFromReprCSliceMut, + TryFromReprCSliceRef, + }, + FfiResult, IntoFfi, ReprC, TryFromReprC, +}; + +#[inline] +const fn is_valid_bool(source: u8) -> bool { + source == 0 || source == 1 +} + +impl<'itm> TryFromReprC<'itm> for bool { + type Source = >::Source; + type Store = (); + + unsafe fn try_from_repr_c(source: Self::Source, _: &mut ()) -> Result { + let source: u8 = TryFromReprC::try_from_repr_c(source, &mut ())?; + + if !is_valid_bool(source) { + return Err(FfiResult::TrapRepresentation); + } + + Ok(source != 0) + } +} +impl<'itm> TryFromReprC<'itm> for &'itm bool { + type Source = <&'itm u8 as TryFromReprC<'itm>>::Source; + type Store = (); + + // False positive - doesn't compile otherwise + #[allow(clippy::let_unit_value)] + unsafe fn try_from_repr_c(source: Self::Source, _: &mut ()) -> Result { + let mut store = (); + let source: &u8 = TryFromReprC::try_from_repr_c(source, &mut store)?; + + if !is_valid_bool(*source) { + return Err(FfiResult::TrapRepresentation); + } + + Ok(&*(source as *const u8).cast::()) + } +} + +impl<'slice> TryFromReprCSliceRef<'slice> for bool { + type Source = >::Source; + type Store = (); + + // False positive - doesn't compile otherwise + #[allow(clippy::let_unit_value)] + unsafe fn try_from_repr_c(source: Self::Source, _: &mut ()) -> Result<&[Self], FfiResult> { + let mut store = (); + let source: &[u8] = TryFromReprC::try_from_repr_c(source, &mut store)?; + + if !source.iter().all(|item| is_valid_bool(*item)) { + return Err(FfiResult::TrapRepresentation); + } + + Ok(&*(source as *const _ as *const _)) + } +} + +impl IntoFfi for bool { + type Target = u8; + + fn into_ffi(self) -> Self::Target { + u8::from(self).into_ffi() + } +} +impl IntoFfi for &bool { + type Target = *const u8; + + fn into_ffi(self) -> Self::Target { + (self as *const bool).cast() + } +} + +impl<'itm> IntoFfiSliceRef<'itm> for bool { + type Target = SliceRef<'itm, u8>; + + fn into_ffi(source: &[Self]) -> Self::Target { + // SAFETY: bool has the same representation as u8 + SliceRef::from_slice(unsafe { &*(source as *const [bool] as *const [u8]) }) + } +} + +impl<'itm> TryFromReprC<'itm> for core::cmp::Ordering { + type Source = >::Source; + type Store = (); + + unsafe fn try_from_repr_c(source: Self::Source, _: &mut ()) -> Result { + let source: i8 = TryFromReprC::try_from_repr_c(source, &mut ())?; + + match source { + -1 => Ok(core::cmp::Ordering::Less), + 0 => Ok(core::cmp::Ordering::Equal), + 1 => Ok(core::cmp::Ordering::Greater), + _ => Err(FfiResult::TrapRepresentation), + } + } +} +impl<'itm> TryFromReprC<'itm> for &'itm core::cmp::Ordering { + type Source = <&'itm i8 as TryFromReprC<'itm>>::Source; + type Store = (); + + // False positive - doesn't compile otherwise + #[allow(clippy::let_unit_value)] + unsafe fn try_from_repr_c(source: Self::Source, _: &mut ()) -> Result { + let mut store = (); + let source: &i8 = TryFromReprC::try_from_repr_c(source, &mut store)?; + + if !(*source == -1 || *source == 0 || *source == 1) { + return Err(FfiResult::TrapRepresentation); + } + + Ok(&*(source as *const i8).cast::()) + } +} + +impl<'slice> TryFromReprCSliceRef<'slice> for core::cmp::Ordering { + type Source = >::Source; + type Store = (); + + // False positive - doesn't compile otherwise + #[allow(clippy::let_unit_value)] + unsafe fn try_from_repr_c(source: Self::Source, _: &mut ()) -> Result<&[Self], FfiResult> { + let mut store = (); + let source: &[i8] = TryFromReprC::try_from_repr_c(source, &mut store)?; + + if !source.iter().all(|e| *e == -1 || *e == 0 || *e == 1) { + return Err(FfiResult::TrapRepresentation); + } + + Ok(&*(source as *const _ as *const _)) + } +} + +impl IntoFfi for core::cmp::Ordering { + type Target = i8; + + fn into_ffi(self) -> Self::Target { + self as i8 + } +} +impl IntoFfi for &core::cmp::Ordering { + type Target = *const i8; + + fn into_ffi(self) -> Self::Target { + (self as *const core::cmp::Ordering).cast() + } +} +impl IntoFfi for &mut core::cmp::Ordering { + type Target = *mut i8; + + fn into_ffi(self) -> Self::Target { + (self as *mut core::cmp::Ordering).cast() + } +} + +impl<'itm> IntoFfiSliceRef<'itm> for core::cmp::Ordering { + type Target = SliceRef<'itm, i8>; + + fn into_ffi(source: &[Self]) -> Self::Target { + // SAFETY: `core::cmp::Ordering` has the same representation as i8 + unsafe { SliceRef::from_slice(&*(source as *const [_] as *const [i8])) } + } +} + +macro_rules! primitive_impls { + ( $( $ty:ty ),+ $(,)? ) => { $( + unsafe impl ReprC for $ty {} + + impl TryFromReprC<'_> for $ty { + type Source = Self; + type Store = (); + + unsafe fn try_from_repr_c(source: Self::Source, _: &mut Self::Store) -> Result { + Ok(source) + } + } + + impl<'itm> TryFromReprCSliceRef<'itm> for $ty { + type Source = SliceRef<'itm, $ty>; + type Store = (); + + unsafe fn try_from_repr_c(source: Self::Source, _: &'itm mut Self::Store) -> Result<&[Self], FfiResult> { + source.into_rust().ok_or(FfiResult::ArgIsNull) + } + } + + impl<'slice> TryFromReprCSliceMut<'slice> for $ty { + type Source = SliceMut<'slice, $ty>; + type Store = (); + + unsafe fn try_from_repr_c(source: Self::Source, _: &mut Self::Store) -> Result<&'slice mut [Self], FfiResult> { + source.into_rust().ok_or(FfiResult::ArgIsNull) + } + } + + impl IntoFfi for $ty { + type Target = Self; + + fn into_ffi(self) -> Self::Target { + self + } + } + + impl<'itm> IntoFfiSliceRef<'itm> for $ty { + type Target = SliceRef<'itm, $ty>; + + fn into_ffi(source: &[Self]) -> Self::Target { + SliceRef::from_slice(source) + } + } + unsafe impl<'slice> IntoFfiSliceMut<'slice> for $ty { + type Target = SliceMut<'slice, $ty>; + + fn into_ffi(source: &mut [Self]) -> Self::Target { + SliceMut::from_slice(source) + } + } )+ + }; +} + +primitive_impls! {u8, u16, u32, u64, u128, i8, i16, i32, i64} diff --git a/ffi/src/slice.rs b/ffi/src/slice.rs new file mode 100644 index 00000000000..92a9822098d --- /dev/null +++ b/ffi/src/slice.rs @@ -0,0 +1,396 @@ +//! Logic related to the conversion of slices to and from FFI-compatible representation + +use core::{marker::PhantomData, mem::MaybeUninit}; + +use crate::{ + owned::LocalSlice, AsReprCRef, FfiResult, IntoFfi, OutPtrOf, Output, ReprC, TryFromReprC, +}; + +/// Trait that facilitates the implementation of [`IntoFfi`] for immutable slices of foreign types +pub trait IntoFfiSliceRef<'slice>: Sized { + /// Immutable slice equivalent of [`IntoFfi::Target`] + type Target: ReprC; + + /// Convert from `&[Self]` into [`Self::Target`] + fn into_ffi(source: &'slice [Self]) -> Self::Target; +} + +/// Trait that facilitates the implementation of [`IntoFfi`] for mutable slices of foreign types +/// +/// # Safety +/// +/// `[Self]` and `[Self::Target]` must have the same representation, i.e. must be transmutable. +/// This is because it's not possible to mutably reference local context across FFI boundary +/// Additionally, if implemented on a non-robust type the invariant that trap representations +/// will never be written into values of `Self` by foreign code must be upheld at all times +pub unsafe trait IntoFfiSliceMut<'slice>: Sized { + /// Mutable slice equivalent of [`IntoFfi::Target`] + type Target: ReprC + 'slice; + + /// Convert from `&mut [Self]` into [`Self::Target`] + fn into_ffi(source: &'slice mut [Self]) -> Self::Target; +} + +/// Trait that facilitates the implementation of [`TryFromReprC`] for immutable slices of foreign types +pub trait TryFromReprCSliceRef<'slice>: Sized { + /// Immutable slice equivalent of [`TryFromReprC::Source`] + type Source: ReprC + Copy; + + /// Type into which state can be stored during conversion. Useful for returning + /// non-owning types but performing some conversion which requires allocation. + /// Serves similar purpose as does context in a closure + type Store: Default; + + /// Convert from [`Self::Source`] into `&[Self]` + /// + /// # Errors + /// + /// * [`FfiResult::ArgIsNull`] - given pointer is null + /// * [`FfiResult::UnknownHandle`] - given id doesn't identify any known handle + /// * [`FfiResult::TrapRepresentation`] - given value contains trap representation + /// + /// # Safety + /// + /// All conversions from a pointer must ensure pointer validity beforehand + unsafe fn try_from_repr_c( + source: Self::Source, + store: &'slice mut Self::Store, + ) -> Result<&'slice [Self], FfiResult>; +} + +/// Trait that facilitates the implementation of [`TryFromReprC`] for mutable slices of foreign types +pub trait TryFromReprCSliceMut<'slice>: Sized { + /// Mutable slice equivalent of [`TryFromReprC::Source`] + type Source: ReprC + Copy; + + /// Type into which state can be stored during conversion. Useful for returning + /// non-owning types but performing some conversion which requires allocation. + /// Serves similar purpose as does context in a closure + type Store: Default; + + /// Perform the conversion from [`Self::Source`] into `&mut [Self]` + /// + /// # Errors + /// + /// * [`FfiResult::ArgIsNull`] - given pointer is null + /// * [`FfiResult::UnknownHandle`] - given id doesn't identify any known handle + /// * [`FfiResult::TrapRepresentation`] - given value contains trap representation + /// + /// # Safety + /// + /// All conversions from a pointer must ensure pointer validity beforehand + unsafe fn try_from_repr_c( + source: Self::Source, + store: &'slice mut Self::Store, + ) -> Result<&'slice mut [Self], FfiResult>; +} + +/// Immutable slice with a defined C ABI layout. Consists of data pointer and length +#[repr(C)] +pub struct SliceRef<'data, T>(*const T, usize, PhantomData<&'data T>); + +/// Mutable slice with a defined C ABI layout. Consists of data pointer and length +#[repr(C)] +pub struct SliceMut<'data, T>(*mut T, usize, PhantomData<&'data mut T>); + +/// Immutable slice with a defined C ABI layout when used as a function return argument. Provides +/// a pointer where data pointer should be stored, and a pointer where length should be stored. +#[repr(C)] +pub struct OutSliceRef(*mut *const T, *mut usize); + +/// Mutable slice with a defined C ABI layout when used as a function return argument. Provides +/// a pointer where data pointer should be stored, and a pointer where length should be stored. +#[repr(C)] +pub struct OutSliceMut(*mut *mut T, *mut usize); + +/// Owned slice with a defined C ABI layout when used as a function return argument. Provides +/// a pointer to the allocation where the data should be copied into, length of the allocation, +/// and a pointer where total length of the data should be stored in the case that the provided +/// allocation is not large enough to store all the data +/// +/// Returned length is [`isize`] to be able to support `None` values when converting types such as [`Option`] +#[repr(C)] +pub struct OutBoxedSlice(*mut T, usize, *mut isize); + +// NOTE: raw pointers are also `Copy` +impl Copy for SliceRef<'_, T> {} +impl Clone for SliceRef<'_, T> { + fn clone(&self) -> Self { + Self(self.0, self.1, PhantomData) + } +} +impl Copy for SliceMut<'_, T> {} +impl Clone for SliceMut<'_, T> { + fn clone(&self) -> Self { + Self(self.0, self.1, PhantomData) + } +} +impl Copy for OutSliceRef {} +impl Clone for OutSliceRef { + fn clone(&self) -> Self { + Self(self.0, self.1) + } +} +impl Copy for OutSliceMut {} +impl Clone for OutSliceMut { + fn clone(&self) -> Self { + Self(self.0, self.1) + } +} +impl Copy for OutBoxedSlice {} +impl Clone for OutBoxedSlice { + fn clone(&self) -> Self { + Self(self.0, self.1, self.2) + } +} + +impl<'slice, T> SliceRef<'slice, T> { + /// Forms a slice from a data pointer and a length. + pub fn from_raw_parts(ptr: *const T, len: usize) -> Self { + Self(ptr, len, PhantomData) + } + + /// Create [`Self`] from shared slice + pub const fn from_slice(slice: &[T]) -> Self { + Self(slice.as_ptr(), slice.len(), PhantomData) + } + + /// Convert [`Self`] into a shared slice. Return `None` if data pointer is null + /// + /// # Safety + /// + /// Data pointer must point to a valid memory + pub unsafe fn into_rust(self) -> Option<&'slice [T]> { + if self.is_null() { + return None; + } + + Some(core::slice::from_raw_parts(self.0, self.1)) + } + + pub(crate) fn null() -> Self { + // TODO: len could be uninitialized + Self(core::ptr::null(), 0, PhantomData) + } + + pub(crate) fn is_null(&self) -> bool { + self.0.is_null() + } +} +impl<'slice, T> SliceMut<'slice, T> { + /// Create [`Self`] from mutable slice + pub fn from_slice(slice: &mut [T]) -> Self { + Self(slice.as_mut_ptr(), slice.len(), PhantomData) + } + + /// Convert [`Self`] into a mutable slice. Return `None` if data pointer is null + /// + /// # Safety + /// + /// Data pointer must point to a valid memory + pub unsafe fn into_rust(self) -> Option<&'slice mut [T]> { + if self.is_null() { + return None; + } + + Some(core::slice::from_raw_parts_mut(self.0, self.1)) + } + + pub(crate) fn null() -> Self { + // TODO: len could be uninitialized + Self(core::ptr::null_mut(), 0, PhantomData) + } + + pub(crate) fn is_null(&self) -> bool { + self.0.is_null() + } +} + +impl OutSliceRef { + /// Construct `Self` from a slice of uninitialized elements + pub fn from_raw(mut data_ptr: Option<&mut *const T>, len_ptr: &mut MaybeUninit) -> Self { + Self( + data_ptr + .take() + .map_or_else(core::ptr::null_mut, |item| <*mut _>::from(item)), + len_ptr.as_mut_ptr(), + ) + } + pub(crate) unsafe fn write_none(self) { + self.0.write(core::ptr::null()); + } +} +impl OutSliceMut { + /// Construct `Self` from a slice of uninitialized elements + pub fn from_raw(mut data_ptr: Option<&mut *mut T>, len_ptr: &mut MaybeUninit) -> Self { + Self( + data_ptr + .take() + .map_or_else(core::ptr::null_mut, |item| <*mut _>::from(item)), + len_ptr.as_mut_ptr(), + ) + } + pub(crate) unsafe fn write_none(self) { + self.0.write(core::ptr::null_mut()); + } +} +impl OutBoxedSlice { + const NONE: isize = -1; + + /// Construct `Self` from a slice of uninitialized elements + pub fn from_uninit_slice( + mut data_ptr: Option<&mut [MaybeUninit]>, + len_ptr: &mut MaybeUninit, + ) -> Self { + let len = data_ptr.as_ref().map_or(0, |slice| slice.len()); + + Self( + data_ptr + .take() + .map_or_else(core::ptr::null_mut, |item| <[_]>::as_mut_ptr(item).cast()), + len, + len_ptr.as_mut_ptr(), + ) + } + + pub(crate) unsafe fn write_none(self) { + self.2.write(Self::NONE); + } +} + +unsafe impl ReprC for SliceRef<'_, T> {} +unsafe impl ReprC for SliceMut<'_, T> {} +unsafe impl ReprC for OutSliceRef {} +unsafe impl ReprC for OutSliceMut {} +unsafe impl ReprC for OutBoxedSlice {} + +impl<'slice, T: 'slice> AsReprCRef<'slice> for SliceRef<'slice, T> { + type Target = Self; + + fn as_ref(&self) -> Self::Target { + *self + } +} + +impl<'slice, T: TryFromReprCSliceRef<'slice>> TryFromReprC<'slice> for &'slice [T] { + type Source = T::Source; + type Store = T::Store; + + unsafe fn try_from_repr_c( + source: Self::Source, + store: &'slice mut Self::Store, + ) -> Result { + TryFromReprCSliceRef::try_from_repr_c(source, store) + } +} +impl<'slice, T: TryFromReprCSliceMut<'slice>> TryFromReprC<'slice> for &'slice mut [T] { + type Source = T::Source; + type Store = T::Store; + + unsafe fn try_from_repr_c( + source: Self::Source, + store: &'slice mut Self::Store, + ) -> Result { + TryFromReprCSliceMut::try_from_repr_c(source, store) + } +} + +impl<'slice, T: IntoFfiSliceRef<'slice>> IntoFfi for &'slice [T] { + type Target = T::Target; + + fn into_ffi(self) -> Self::Target { + IntoFfiSliceRef::into_ffi(self) + } +} +impl<'slice, T: IntoFfiSliceMut<'slice>> IntoFfi for &'slice mut [T] { + type Target = T::Target; + + fn into_ffi(self) -> Self::Target { + IntoFfiSliceMut::into_ffi(self) + } +} +impl<'slice, T: IntoFfiSliceRef<'slice>> IntoFfiSliceRef<'slice> for &'slice [T] { + type Target = LocalSlice; + + fn into_ffi(source: &[Self]) -> Self::Target { + source + .iter() + .map(|item| IntoFfiSliceRef::into_ffi(item)) + .collect() + } +} +impl<'slice, T: IntoFfiSliceRef<'slice>> IntoFfiSliceRef<'slice> for &'slice mut [T] { + type Target = LocalSlice; + + fn into_ffi(source: &'slice [Self]) -> Self::Target { + source + .iter() + .map(|item| IntoFfiSliceRef::into_ffi(item)) + .collect() + } +} + +impl<'data, T> OutPtrOf> for OutSliceRef { + unsafe fn write(self, source: SliceRef<'data, T>) -> Result<(), FfiResult> { + if self.1.is_null() { + return Err(FfiResult::ArgIsNull); + } + + if self.0.is_null() { + self.write_none(); + } else { + self.0.write(source.0); + self.1.write(source.1); + } + + Ok(()) + } +} +impl<'data, T> OutPtrOf> for OutSliceMut { + unsafe fn write(self, source: SliceMut<'data, T>) -> Result<(), FfiResult> { + if self.1.is_null() { + return Err(FfiResult::ArgIsNull); + } + + if self.0.is_null() { + self.write_none(); + } else { + self.0.write(source.0); + self.1.write(source.1); + } + + Ok(()) + } +} +impl OutPtrOf> for OutBoxedSlice { + unsafe fn write(self, source: LocalSlice) -> Result<(), FfiResult> { + if self.2.is_null() { + return Err(FfiResult::ArgIsNull); + } + + // slice len is never larger than `isize::MAX` + #[allow(clippy::expect_used)] + source.into_rust().map_or_else( + || self.write_none(), + |slice| { + self.2 + .write(slice.len().try_into().expect("Allocation too large")); + + if !self.0.is_null() { + for (i, elem) in slice.iter().take(self.1).enumerate() { + self.0.add(i).write(*elem); + } + } + }, + ); + + Ok(()) + } +} + +impl<'data, T> Output for SliceRef<'data, T> { + type OutPtr = OutSliceRef; +} +impl<'data, T> Output for SliceMut<'data, T> { + type OutPtr = OutSliceMut; +} diff --git a/ffi/tests/bindgen_derive.rs b/ffi/tests/bindgen_derive.rs deleted file mode 100644 index a8c0b55a322..00000000000 --- a/ffi/tests/bindgen_derive.rs +++ /dev/null @@ -1,59 +0,0 @@ -#![allow(unsafe_code, clippy::restriction, clippy::pedantic)] - -use std::mem::MaybeUninit; - -use getset::{Getters, MutGetters, Setters}; -use iroha_ffi::ffi_bindgen; - -#[derive(Debug, Clone, PartialEq, Eq)] -pub struct Name(String); - -#[ffi_bindgen] -#[derive(Setters, Getters, MutGetters)] -#[getset(get = "pub")] -pub struct FfiStruct { - #[getset(set = "pub", get_mut = "pub")] - id: u32, - name: Option, - is_null: Option, -} - -#[test] -fn getset_get() { - let init_name = Name("Name".to_owned()); - let ffi_struct: *mut _ = &mut FfiStruct { - id: 1, - name: Some(init_name.clone()), - is_null: None, - }; - - let mut id = MaybeUninit::<*mut u32>::new(core::ptr::null_mut()); - let mut name = MaybeUninit::<*const Name>::new(core::ptr::null()); - let mut is_null = MaybeUninit::<*const Name>::new(core::ptr::null()); - - unsafe { - FfiStruct__id_mut(ffi_struct, id.as_mut_ptr()); - let id = &mut *id.assume_init(); - assert_eq!(&mut 1, id); - - FfiStruct__name(ffi_struct, name.as_mut_ptr()); - FfiStruct__is_null(ffi_struct, is_null.as_mut_ptr()); - - let name_ptr = name.assume_init(); - let is_null_ptr = is_null.assume_init(); - - let name = if !name_ptr.is_null() { - Some(&*name_ptr) - } else { - None - }; - let is_null = if !is_null_ptr.is_null() { - Some(&*is_null_ptr) - } else { - None - }; - - assert_eq!(Some(&init_name), name); - assert_eq!(None, is_null); - } -} diff --git a/ffi/tests/ffi_bindgen.rs b/ffi/tests/ffi_export.rs similarity index 50% rename from ffi/tests/ffi_bindgen.rs rename to ffi/tests/ffi_export.rs index 832ec36cbc1..1ed7317ca78 100644 --- a/ffi/tests/ffi_bindgen.rs +++ b/ffi/tests/ffi_export.rs @@ -2,11 +2,14 @@ use std::{collections::BTreeMap, mem::MaybeUninit}; -use iroha_ffi::{ffi_bindgen, gen_ffi_impl, handles, FfiResult, Pair}; +use iroha_ffi::{ + ffi_export, gen_ffi_impl, handles, slice::OutBoxedSlice, AsReprCRef, FfiResult, FfiTuple2, + Handle, IntoFfi, TryFromFfi, TryFromReprC, +}; -#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, IntoFfi, TryFromFfi)] pub struct Name(String); -#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, IntoFfi, TryFromFfi)] pub struct Value(String); fn get_default_params() -> [(Name, Value); 2] { @@ -16,8 +19,8 @@ fn get_default_params() -> [(Name, Value); 2] { ] } -#[ffi_bindgen] -#[derive(Clone)] +#[ffi_export] +#[derive(Clone, IntoFfi, TryFromFfi)] pub struct FfiStruct { name: Option, tokens: Vec, @@ -27,17 +30,20 @@ pub struct FfiStruct { handles! {0, FfiStruct} gen_ffi_impl! {Drop: FfiStruct} -#[ffi_bindgen] +#[ffi_export] impl FfiStruct { /// New - pub fn new(name: impl Into) -> Self { + pub fn new(name: Name) -> Self { Self { - name: Some(name.into()), + name: Some(name), tokens: Vec::new(), params: BTreeMap::default(), } } + /// Consume self + pub fn consume_self(self) {} + /// With tokens #[must_use] pub fn with_tokens(mut self, tokens: impl IntoIterator>) -> Self { @@ -62,6 +68,16 @@ impl FfiStruct { self.params.iter() } + /// Tokens + pub fn tokens(&self) -> &[Value] { + &self.tokens + } + + /// Tokens mut + pub fn name_mut(&mut self) -> Option<&mut Name> { + self.name.as_mut() + } + /// Fallible int output pub fn fallible_int_output(flag: bool) -> Result { if flag { @@ -72,7 +88,7 @@ impl FfiStruct { } } -fn get_new_struct() -> *mut FfiStruct { +fn get_new_struct() -> FfiStruct { let name = Name(String::from("X")); unsafe { @@ -80,26 +96,23 @@ fn get_new_struct() -> *mut FfiStruct { assert_eq!( FfiResult::Ok, - FfiStruct__new(&name, ffi_struct.as_mut_ptr()) + FfiStruct__new(IntoFfi::into_ffi(name), ffi_struct.as_mut_ptr()) ); let ffi_struct = ffi_struct.assume_init(); assert!(!ffi_struct.is_null()); - ffi_struct + TryFromReprC::try_from_repr_c(ffi_struct, &mut ()).unwrap() } } #[allow(trivial_casts)] -fn get_new_struct_with_params() -> *mut FfiStruct { - let ffi_struct = get_new_struct(); +fn get_new_struct_with_params() -> FfiStruct { + let mut ffi_struct = get_new_struct(); let params = get_default_params(); - let params_ffi: Vec<_> = params - .iter() - .map(|(key, val)| Pair(key as *const _, val as *const _)) - .collect(); + let params_ffi = params.into_ffi(); assert_eq!(FfiResult::Ok, unsafe { - FfiStruct__with_params(ffi_struct, params_ffi.as_ptr(), params_ffi.len()) + FfiStruct__with_params(IntoFfi::into_ffi(&mut ffi_struct), params_ffi.as_ref()) }); ffi_struct @@ -110,49 +123,70 @@ fn constructor() { let ffi_struct = get_new_struct(); unsafe { - assert_eq!(Some(Name(String::from('X'))), (*ffi_struct).name); - assert!((*ffi_struct).params.is_empty()); + assert_eq!(Some(Name(String::from('X'))), ffi_struct.name); + assert!(ffi_struct.params.is_empty()); - assert_eq!(FfiResult::Ok, __drop(FfiStruct::ID, ffi_struct.cast())); + assert_eq!( + FfiResult::Ok, + __drop(FfiStruct::ID, ffi_struct.into_ffi().cast()) + ); } } #[test] -#[allow(trivial_casts)] -fn into_iter_item_impl_into() { - let ffi_struct = get_new_struct(); - - let tokens = vec![ - Value(String::from("My omen")), - Value(String::from("Your omen")), - ]; - let tokens_ffi: Vec<_> = tokens.iter().map(|t| t as *const _).collect(); +fn builder_method() { + let ffi_struct = get_new_struct_with_params(); unsafe { + assert_eq!(2, ffi_struct.params.len()); + assert_eq!( + ffi_struct.params, + get_default_params().into_iter().collect() + ); + assert_eq!( FfiResult::Ok, - FfiStruct__with_tokens(ffi_struct, tokens_ffi.as_ptr(), tokens_ffi.len()) + __drop(FfiStruct::ID, ffi_struct.into_ffi().cast()) ); + } +} - assert_eq!(2, (*ffi_struct).tokens.len()); - assert_eq!((*ffi_struct).tokens, tokens); +#[test] +fn consume_self() { + let ffi_struct = get_new_struct(); - assert_eq!(FfiResult::Ok, __drop(FfiStruct::ID, ffi_struct.cast())); + unsafe { + assert_eq!( + FfiResult::Ok, + FfiStruct__consume_self(ffi_struct.into_ffi().cast()) + ); } } #[test] -fn builder_method() { - let ffi_struct = get_new_struct_with_params(); +#[allow(trivial_casts)] +fn into_iter_item_impl_into() { + let tokens = vec![ + Value(String::from("My omen")), + Value(String::from("Your omen")), + ]; + + let mut ffi_struct = get_new_struct(); + let tokens_ffi = tokens.clone().into_ffi(); unsafe { - assert_eq!(2, (*ffi_struct).params.len()); assert_eq!( - (*ffi_struct).params, - get_default_params().into_iter().collect() + FfiResult::Ok, + FfiStruct__with_tokens(IntoFfi::into_ffi(&mut ffi_struct), tokens_ffi.as_ref()) ); - assert_eq!(FfiResult::Ok, __drop(FfiStruct::ID, ffi_struct.cast())); + assert_eq!(2, ffi_struct.tokens.len()); + assert_eq!(ffi_struct.tokens, tokens); + + assert_eq!( + FfiResult::Ok, + __drop(FfiStruct::ID, ffi_struct.into_ffi().cast()) + ); } } @@ -165,19 +199,30 @@ fn return_option() { let name1 = Name(String::from("Non")); assert_eq!(FfiResult::Ok, unsafe { - FfiStruct__get_param(ffi_struct, &name1, param1.as_mut_ptr()) + FfiStruct__get_param(IntoFfi::into_ffi(&ffi_struct), &name1, param1.as_mut_ptr()) }); - unsafe { assert!(param1.assume_init().is_null()) }; + let param1 = unsafe { param1.assume_init() }; + assert!(param1.is_null()); + let mut store = (); + let param1: Option<&Value> = + unsafe { TryFromReprC::try_from_repr_c(param1, &mut store).unwrap() }; + assert!(param1.is_none()); let name2 = Name(String::from("Nomen")); assert_eq!(FfiResult::Ok, unsafe { - FfiStruct__get_param(ffi_struct, &name2, param2.as_mut_ptr()) + FfiStruct__get_param(IntoFfi::into_ffi(&ffi_struct), &name2, param2.as_mut_ptr()) }); unsafe { - assert!(!param2.assume_init().is_null()); - assert_eq!(&Value(String::from("Omen")), &*param2.assume_init()); - assert_eq!(FfiResult::Ok, __drop(FfiStruct::ID, ffi_struct.cast())); + let param2 = param2.assume_init(); + assert!(!param2.is_null()); + let mut store = (); + let param2: Option<&Value> = TryFromReprC::try_from_repr_c(param2, &mut store).unwrap(); + assert_eq!(Some(&Value(String::from("Omen"))), param2); + assert_eq!( + FfiResult::Ok, + __drop(FfiStruct::ID, ffi_struct.into_ffi().cast()) + ); } } @@ -186,18 +231,18 @@ fn empty_return_iterator() { let ffi_struct = get_new_struct_with_params(); let mut params_len = MaybeUninit::new(0); + let out_params = OutBoxedSlice::from_uninit_slice(None, &mut params_len); + unsafe { assert_eq!( FfiResult::Ok, - FfiStruct__params( - ffi_struct, - core::ptr::null_mut(), - 0_usize, - params_len.as_mut_ptr(), - ) + FfiStruct__params(IntoFfi::into_ffi(&ffi_struct), out_params) ); assert!(params_len.assume_init() == 2); - assert_eq!(FfiResult::Ok, __drop(FfiStruct::ID, ffi_struct.cast())); + assert_eq!( + FfiResult::Ok, + __drop(FfiStruct::ID, ffi_struct.into_ffi().cast()) + ); } } @@ -205,31 +250,31 @@ fn empty_return_iterator() { fn return_iterator() { let ffi_struct = get_new_struct_with_params(); let mut params_len = MaybeUninit::new(0); - let mut params = Vec::with_capacity(1); + let mut params = [MaybeUninit::new(FfiTuple2( + core::ptr::null(), + core::ptr::null(), + ))]; + + let out_params = OutBoxedSlice::from_uninit_slice(Some(params.as_mut_slice()), &mut params_len); unsafe { assert_eq!( FfiResult::Ok, - FfiStruct__params( - ffi_struct, - params.as_mut_ptr(), - params.capacity(), - params_len.as_mut_ptr(), - ) + FfiStruct__params(IntoFfi::into_ffi(&ffi_struct), out_params) ); - assert!(params_len.assume_init() == 2); - params.set_len(1); + assert_eq!(params_len.assume_init(), 2); - assert!(params - .iter() - .map(|&Pair(key, val)| (&*key, &*val)) - .eq(get_default_params() - .iter() - .take(1) - .map(|pair| (&pair.0, &pair.1)))); + let mut store = Default::default(); + let item: (&Name, &Value) = + <(_, _) as TryFromReprC>::try_from_repr_c(params[0].assume_init(), &mut store).unwrap(); + let expected = get_default_params(); + assert_eq!((&expected[0].0, &expected[0].1), item); - assert_eq!(FfiResult::Ok, __drop(FfiStruct::ID, ffi_struct.cast())); + assert_eq!( + FfiResult::Ok, + __drop(FfiStruct::ID, ffi_struct.into_ffi().cast()) + ); } } diff --git a/ffi/tests/gen_shared_fns.rs b/ffi/tests/gen_shared_fns.rs index 38c37cfb395..0e1286a89c3 100644 --- a/ffi/tests/gen_shared_fns.rs +++ b/ffi/tests/gen_shared_fns.rs @@ -2,14 +2,16 @@ use std::{cmp::Ordering, mem::MaybeUninit}; -use iroha_ffi::{ffi_bindgen, gen_ffi_impl, handles, FfiResult}; +use iroha_ffi::{ + ffi_export, gen_ffi_impl, handles, FfiResult, Handle, IntoFfi, TryFromFfi, TryFromReprC, +}; -#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, IntoFfi, TryFromFfi)] pub struct FfiStruct1 { name: String, } -#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, IntoFfi, TryFromFfi)] pub struct FfiStruct2 { name: String, } @@ -20,7 +22,7 @@ gen_ffi_impl! {Clone: FfiStruct1, FfiStruct2} gen_ffi_impl! {Eq: FfiStruct1, FfiStruct2} gen_ffi_impl! {Ord: FfiStruct1, FfiStruct2} -#[ffi_bindgen] +#[ffi_export] impl FfiStruct1 { /// New pub fn new(name: String) -> Self { @@ -34,7 +36,7 @@ fn gen_shared_fns() { let ffi_struct1 = unsafe { let mut ffi_struct = MaybeUninit::new(core::ptr::null_mut()); - assert_eq! {FfiResult::Ok, FfiStruct1__new(&name, ffi_struct.as_mut_ptr())}; + assert_eq! {FfiResult::Ok, FfiStruct1__new(IntoFfi::into_ffi(name.as_str()), ffi_struct.as_mut_ptr())}; let ffi_struct = ffi_struct.assume_init(); assert!(!ffi_struct.is_null()); assert_eq!(FfiStruct1 { name }, *ffi_struct); @@ -51,33 +53,41 @@ fn gen_shared_fns() { cloned.as_mut_ptr().cast(), ); - let cloned = cloned.assume_init(); - assert_eq!(*ffi_struct1, *cloned); + let cloned = TryFromReprC::try_from_repr_c(cloned.assume_init(), &mut ()).unwrap(); + assert_eq!(*ffi_struct1, cloned); cloned }; - let mut is_equal = MaybeUninit::::new(false); + let mut is_equal = MaybeUninit::::new(1); + let cloned_ptr = IntoFfi::into_ffi(&cloned); + __eq( FfiStruct1::ID, ffi_struct1.cast(), - cloned.cast(), + cloned_ptr.cast(), is_equal.as_mut_ptr(), ); - let is_equal = is_equal.assume_init(); + let is_equal: bool = + TryFromReprC::try_from_repr_c(is_equal.assume_init(), &mut ()).unwrap(); assert!(is_equal); - let mut ordering = MaybeUninit::::new(Ordering::Less); + // TODO: Fix + let mut ordering = MaybeUninit::::new(1); __ord( FfiStruct1::ID, ffi_struct1.cast(), - cloned.cast(), + cloned_ptr.cast(), ordering.as_mut_ptr(), ); - let ordering = ordering.assume_init(); + let ordering: Ordering = + TryFromReprC::try_from_repr_c(ordering.assume_init(), &mut ()).unwrap(); assert_eq!(ordering, Ordering::Equal); assert_eq!(FfiResult::Ok, __drop(FfiStruct1::ID, ffi_struct1.cast())); - assert_eq!(FfiResult::Ok, __drop(FfiStruct1::ID, cloned.cast())); + assert_eq!( + FfiResult::Ok, + __drop(FfiStruct1::ID, cloned.into_ffi().cast()) + ); } } diff --git a/ffi/tests/getset.rs b/ffi/tests/getset.rs new file mode 100644 index 00000000000..c2b72fa29f9 --- /dev/null +++ b/ffi/tests/getset.rs @@ -0,0 +1,44 @@ +#![allow(unsafe_code, clippy::restriction, clippy::pedantic)] + +use std::mem::MaybeUninit; + +use getset::{Getters, MutGetters, Setters}; +use iroha_ffi::{ffi_export, IntoFfi, TryFromFfi}; + +#[derive(Debug, Clone, PartialEq, Eq, IntoFfi, TryFromFfi)] +pub struct Name(String); + +#[derive(Clone, Setters, Getters, MutGetters, IntoFfi, TryFromFfi)] +#[ffi_export] +#[getset(get = "pub")] +pub struct FfiStruct { + #[getset(set = "pub", get_mut = "pub")] + id: u32, + name: Name, +} + +#[test] +fn getset_get() { + let init_name = Name("Name".to_owned()); + let ffi_struct = &mut FfiStruct { + id: 1, + name: init_name.clone(), + }; + + let mut id = MaybeUninit::<*mut u32>::new(core::ptr::null_mut()); + let mut name = MaybeUninit::<*const Name>::new(core::ptr::null()); + + unsafe { + FfiStruct__set_id(<*mut _>::from(ffi_struct), 2); + assert_eq!(&2, ffi_struct.id()); + + FfiStruct__id_mut(<*mut _>::from(ffi_struct), id.as_mut_ptr()); + let id = &mut *id.assume_init(); + assert_eq!(&mut 2, id); + + FfiStruct__name(ffi_struct, name.as_mut_ptr()); + let name = &*name.assume_init(); + + assert_eq!(&init_name, name); + } +} diff --git a/permissions_validators/src/lib.rs b/permissions_validators/src/lib.rs index b8f8f185d3f..e21d1f8ea3a 100644 --- a/permissions_validators/src/lib.rs +++ b/permissions_validators/src/lib.rs @@ -90,7 +90,7 @@ macro_rules! declare_token { impl $ident { /// Get associated [`PermissionToken`](iroha_data_model::permissions::PermissionToken) name. pub fn name() -> &'static Name { - static NAME: once_cell::sync::Lazy = + static NAME: once_cell::sync::Lazy = once_cell::sync::Lazy::new(|| $string.parse().expect("Tested. Works.")); &NAME } diff --git a/schema/gen/src/lib.rs b/schema/gen/src/lib.rs index 5e413701b9f..8be739cd06c 100644 --- a/schema/gen/src/lib.rs +++ b/schema/gen/src/lib.rs @@ -13,7 +13,7 @@ macro_rules! schemas { ($($t:ty),* $(,)?) => {{ let mut out = MetaMap::new(); $(<$t as IntoSchema>::schema(&mut out);)* - out + out }}; } diff --git a/schema/src/lib.rs b/schema/src/lib.rs index caebcc6c9dc..79853f5149a 100644 --- a/schema/src/lib.rs +++ b/schema/src/lib.rs @@ -353,12 +353,9 @@ impl IntoSchema for core::time::Duration { types: vec![u64::type_name(), u32::type_name()], }) }); - if !map.contains_key("u64") { - u64::schema(map); - } - if !map.contains_key("u32") { - u32::schema(map); - } + + u32::schema(map); + u64::schema(map); } }