diff options
author | Inna Palant <ipalant@google.com> | 2024-04-19 00:24:59 +0000 |
---|---|---|
committer | Inna Palant <ipalant@google.com> | 2024-04-19 00:24:59 +0000 |
commit | 1ce8a6d40d88eb71a6df329096c957b7673594d6 (patch) | |
tree | 6c26dee5ee070205e1b6ec5baa23fc8287130c79 | |
parent | 8c07b457007ff0a6726ad0b3f07c3ac63a315e9f (diff) | |
parent | cb3e01cfbdf0577e1264387fff074a3a16586567 (diff) | |
download | uniffi_core-1ce8a6d40d88eb71a6df329096c957b7673594d6.tar.gz |
Merge remote-tracking branch 'origin/upstream'
-rw-r--r-- | .cargo_vcs_info.json | 6 | ||||
-rw-r--r-- | Cargo.lock | 191 | ||||
-rw-r--r-- | Cargo.toml | 60 | ||||
-rw-r--r-- | LICENSE | 373 | ||||
-rw-r--r-- | METADATA | 20 | ||||
-rw-r--r-- | MODULE_LICENSE_MPL | 0 | ||||
-rw-r--r-- | OWNERS | 2 | ||||
-rw-r--r-- | README.md | 79 | ||||
-rw-r--r-- | cargo_embargo.json | 3 | ||||
-rw-r--r-- | release.toml | 15 | ||||
-rw-r--r-- | src/ffi/callbackinterface.rs | 309 | ||||
-rw-r--r-- | src/ffi/ffidefault.rs | 58 | ||||
-rw-r--r-- | src/ffi/foreignbytes.rs | 118 | ||||
-rw-r--r-- | src/ffi/foreigncallbacks.rs | 80 | ||||
-rw-r--r-- | src/ffi/mod.rs | 21 | ||||
-rw-r--r-- | src/ffi/rustbuffer.rs | 355 | ||||
-rw-r--r-- | src/ffi/rustcalls.rs | 245 | ||||
-rw-r--r-- | src/ffi/rustfuture/future.rs | 320 | ||||
-rw-r--r-- | src/ffi/rustfuture/mod.rs | 126 | ||||
-rw-r--r-- | src/ffi/rustfuture/scheduler.rs | 96 | ||||
-rw-r--r-- | src/ffi/rustfuture/tests.rs | 223 | ||||
-rw-r--r-- | src/ffi_converter_impls.rs | 531 | ||||
-rw-r--r-- | src/ffi_converter_traits.rs | 466 | ||||
-rw-r--r-- | src/lib.rs | 322 | ||||
-rw-r--r-- | src/metadata.rs | 277 | ||||
-rw-r--r-- | src/panichook.rs | 34 |
26 files changed, 4330 insertions, 0 deletions
diff --git a/.cargo_vcs_info.json b/.cargo_vcs_info.json new file mode 100644 index 0000000..870d019 --- /dev/null +++ b/.cargo_vcs_info.json @@ -0,0 +1,6 @@ +{ + "git": { + "sha1": "d5332be35ef497255f7ce49debfd917f6a1009c7" + }, + "path_in_vcs": "uniffi_core" +}
\ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..c52aea6 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,191 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "addr2line" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a30b2e23b9e17a9f90641c7ab1549cd9b44f296d3ccbf309d2863cfe398a0cb" +dependencies = [ + "gimli", +] + +[[package]] +name = "adler" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" + +[[package]] +name = "anyhow" +version = "1.0.81" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0952808a6c2afd1aa8947271f3a60f1a6763c7b912d210184c5149b5cf147247" + +[[package]] +name = "async-compat" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f68a707c1feb095d8c07f8a65b9f506b117d30af431cab89374357de7c11461b" +dependencies = [ + "futures-core", + "futures-io", + "once_cell", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "backtrace" +version = "0.3.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2089b7e3f35b9dd2d0ed921ead4f6d318c27680d4a5bd167b3ee120edb105837" +dependencies = [ + "addr2line", + "cc", + "cfg-if", + "libc", + "miniz_oxide", + "object", + "rustc-demangle", +] + +[[package]] +name = "bytes" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2bd12c1caf447e69cd4528f47f94d203fd2582878ecb9e9465484c4148a8223" + +[[package]] +name = "camino" +version = "1.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c59e92b5a388f549b863a7bea62612c09f24c8393560709a54558a9abdfb3b9c" + +[[package]] +name = "cc" +version = "1.0.90" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8cd6604a82acf3039f1144f54b8eb34e91ffba622051189e71b781822d5ee1f5" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "futures-core" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d" + +[[package]] +name = "futures-io" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a44623e20b9681a318efdd71c299b6b222ed6f231972bfe2f224ebad6311f0c1" + +[[package]] +name = "gimli" +version = "0.28.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253" + +[[package]] +name = "libc" +version = "0.2.153" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd" + +[[package]] +name = "log" +version = "0.4.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c" + +[[package]] +name = "memchr" +version = "2.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "523dc4f511e55ab87b694dc30d0f820d60906ef06413f93d4d7a1385599cc149" + +[[package]] +name = "miniz_oxide" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d811f3e15f28568be3407c8e7fdb6514c1cda3cb30683f15b6a1a1dc4ea14a7" +dependencies = [ + "adler", +] + +[[package]] +name = "object" +version = "0.32.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6a622008b6e321afc04970976f62ee297fdbaa6f95318ca343e3eebb9648441" +dependencies = [ + "memchr", +] + +[[package]] +name = "once_cell" +version = "1.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" + +[[package]] +name = "oneshot-uniffi" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c548d5c78976f6955d72d0ced18c48ca07030f7a1d4024529fedd7c1c01b29c" + +[[package]] +name = "paste" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de3145af08024dea9fa9914f381a17b8fc6034dfb00f3a84013f7ff43f29ed4c" + +[[package]] +name = "pin-project-lite" +version = "0.2.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8afb450f006bf6385ca15ef45d71d2288452bc3683ce2e2cacc0d18e4be60b58" + +[[package]] +name = "rustc-demangle" +version = "0.1.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" + +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + +[[package]] +name = "tokio" +version = "1.36.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61285f6515fa018fb2d1e46eb21223fff441ee8db5d0f1435e8ab4f5cdb80931" +dependencies = [ + "backtrace", + "pin-project-lite", +] + +[[package]] +name = "uniffi_core" +version = "0.26.1" +dependencies = [ + "anyhow", + "async-compat", + "bytes", + "camino", + "log", + "once_cell", + "oneshot-uniffi", + "paste", + "static_assertions", +] diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..0b74865 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,60 @@ +# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO +# +# When uploading crates to the registry Cargo will automatically +# "normalize" Cargo.toml files for maximal compatibility +# with all versions of Cargo and also rewrite `path` dependencies +# to registry (e.g., crates.io) dependencies. +# +# If you are reading this file be aware that the original Cargo.toml +# will likely look very different (and much more reasonable). +# See Cargo.toml.orig for the original contents. + +[package] +edition = "2021" +name = "uniffi_core" +version = "0.26.1" +authors = ["Firefox Sync Team <sync-team@mozilla.com>"] +description = "a multi-language bindings generator for rust (runtime support code)" +homepage = "https://mozilla.github.io/uniffi-rs" +documentation = "https://mozilla.github.io/uniffi-rs" +readme = "README.md" +keywords = [ + "ffi", + "bindgen", +] +license = "MPL-2.0" +repository = "https://github.com/mozilla/uniffi-rs" + +[dependencies.anyhow] +version = "1" + +[dependencies.async-compat] +version = "0.2.1" +optional = true + +[dependencies.bytes] +version = "1.3" + +[dependencies.camino] +version = "1.0.8" + +[dependencies.log] +version = "0.4" + +[dependencies.once_cell] +version = "1.10.0" + +[dependencies.oneshot] +version = "0.1.5" +features = ["async"] +package = "oneshot-uniffi" + +[dependencies.paste] +version = "1.0" + +[dependencies.static_assertions] +version = "1.1.0" + +[features] +default = [] +tokio = ["dep:async-compat"] @@ -0,0 +1,373 @@ +Mozilla Public License Version 2.0 +================================== + +1. Definitions +-------------- + +1.1. "Contributor" + means each individual or legal entity that creates, contributes to + the creation of, or owns Covered Software. + +1.2. "Contributor Version" + means the combination of the Contributions of others (if any) used + by a Contributor and that particular Contributor's Contribution. + +1.3. "Contribution" + means Covered Software of a particular Contributor. + +1.4. "Covered Software" + means Source Code Form to which the initial Contributor has attached + the notice in Exhibit A, the Executable Form of such Source Code + Form, and Modifications of such Source Code Form, in each case + including portions thereof. + +1.5. "Incompatible With Secondary Licenses" + means + + (a) that the initial Contributor has attached the notice described + in Exhibit B to the Covered Software; or + + (b) that the Covered Software was made available under the terms of + version 1.1 or earlier of the License, but not also under the + terms of a Secondary License. + +1.6. "Executable Form" + means any form of the work other than Source Code Form. + +1.7. "Larger Work" + means a work that combines Covered Software with other material, in + a separate file or files, that is not Covered Software. + +1.8. "License" + means this document. + +1.9. "Licensable" + means having the right to grant, to the maximum extent possible, + whether at the time of the initial grant or subsequently, any and + all of the rights conveyed by this License. + +1.10. "Modifications" + means any of the following: + + (a) any file in Source Code Form that results from an addition to, + deletion from, or modification of the contents of Covered + Software; or + + (b) any new file in Source Code Form that contains any Covered + Software. + +1.11. "Patent Claims" of a Contributor + means any patent claim(s), including without limitation, method, + process, and apparatus claims, in any patent Licensable by such + Contributor that would be infringed, but for the grant of the + License, by the making, using, selling, offering for sale, having + made, import, or transfer of either its Contributions or its + Contributor Version. + +1.12. "Secondary License" + means either the GNU General Public License, Version 2.0, the GNU + Lesser General Public License, Version 2.1, the GNU Affero General + Public License, Version 3.0, or any later versions of those + licenses. + +1.13. "Source Code Form" + means the form of the work preferred for making modifications. + +1.14. "You" (or "Your") + means an individual or a legal entity exercising rights under this + License. For legal entities, "You" includes any entity that + controls, is controlled by, or is under common control with You. For + purposes of this definition, "control" means (a) the power, direct + or indirect, to cause the direction or management of such entity, + whether by contract or otherwise, or (b) ownership of more than + fifty percent (50%) of the outstanding shares or beneficial + ownership of such entity. + +2. License Grants and Conditions +-------------------------------- + +2.1. Grants + +Each Contributor hereby grants You a world-wide, royalty-free, +non-exclusive license: + +(a) under intellectual property rights (other than patent or trademark) + Licensable by such Contributor to use, reproduce, make available, + modify, display, perform, distribute, and otherwise exploit its + Contributions, either on an unmodified basis, with Modifications, or + as part of a Larger Work; and + +(b) under Patent Claims of such Contributor to make, use, sell, offer + for sale, have made, import, and otherwise transfer either its + Contributions or its Contributor Version. + +2.2. Effective Date + +The licenses granted in Section 2.1 with respect to any Contribution +become effective for each Contribution on the date the Contributor first +distributes such Contribution. + +2.3. Limitations on Grant Scope + +The licenses granted in this Section 2 are the only rights granted under +this License. No additional rights or licenses will be implied from the +distribution or licensing of Covered Software under this License. +Notwithstanding Section 2.1(b) above, no patent license is granted by a +Contributor: + +(a) for any code that a Contributor has removed from Covered Software; + or + +(b) for infringements caused by: (i) Your and any other third party's + modifications of Covered Software, or (ii) the combination of its + Contributions with other software (except as part of its Contributor + Version); or + +(c) under Patent Claims infringed by Covered Software in the absence of + its Contributions. + +This License does not grant any rights in the trademarks, service marks, +or logos of any Contributor (except as may be necessary to comply with +the notice requirements in Section 3.4). + +2.4. Subsequent Licenses + +No Contributor makes additional grants as a result of Your choice to +distribute the Covered Software under a subsequent version of this +License (see Section 10.2) or under the terms of a Secondary License (if +permitted under the terms of Section 3.3). + +2.5. Representation + +Each Contributor represents that the Contributor believes its +Contributions are its original creation(s) or it has sufficient rights +to grant the rights to its Contributions conveyed by this License. + +2.6. Fair Use + +This License is not intended to limit any rights You have under +applicable copyright doctrines of fair use, fair dealing, or other +equivalents. + +2.7. Conditions + +Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted +in Section 2.1. + +3. Responsibilities +------------------- + +3.1. Distribution of Source Form + +All distribution of Covered Software in Source Code Form, including any +Modifications that You create or to which You contribute, must be under +the terms of this License. You must inform recipients that the Source +Code Form of the Covered Software is governed by the terms of this +License, and how they can obtain a copy of this License. You may not +attempt to alter or restrict the recipients' rights in the Source Code +Form. + +3.2. Distribution of Executable Form + +If You distribute Covered Software in Executable Form then: + +(a) such Covered Software must also be made available in Source Code + Form, as described in Section 3.1, and You must inform recipients of + the Executable Form how they can obtain a copy of such Source Code + Form by reasonable means in a timely manner, at a charge no more + than the cost of distribution to the recipient; and + +(b) You may distribute such Executable Form under the terms of this + License, or sublicense it under different terms, provided that the + license for the Executable Form does not attempt to limit or alter + the recipients' rights in the Source Code Form under this License. + +3.3. Distribution of a Larger Work + +You may create and distribute a Larger Work under terms of Your choice, +provided that You also comply with the requirements of this License for +the Covered Software. If the Larger Work is a combination of Covered +Software with a work governed by one or more Secondary Licenses, and the +Covered Software is not Incompatible With Secondary Licenses, this +License permits You to additionally distribute such Covered Software +under the terms of such Secondary License(s), so that the recipient of +the Larger Work may, at their option, further distribute the Covered +Software under the terms of either this License or such Secondary +License(s). + +3.4. Notices + +You may not remove or alter the substance of any license notices +(including copyright notices, patent notices, disclaimers of warranty, +or limitations of liability) contained within the Source Code Form of +the Covered Software, except that You may alter any license notices to +the extent required to remedy known factual inaccuracies. + +3.5. Application of Additional Terms + +You may choose to offer, and to charge a fee for, warranty, support, +indemnity or liability obligations to one or more recipients of Covered +Software. However, You may do so only on Your own behalf, and not on +behalf of any Contributor. You must make it absolutely clear that any +such warranty, support, indemnity, or liability obligation is offered by +You alone, and You hereby agree to indemnify every Contributor for any +liability incurred by such Contributor as a result of warranty, support, +indemnity or liability terms You offer. You may include additional +disclaimers of warranty and limitations of liability specific to any +jurisdiction. + +4. Inability to Comply Due to Statute or Regulation +--------------------------------------------------- + +If it is impossible for You to comply with any of the terms of this +License with respect to some or all of the Covered Software due to +statute, judicial order, or regulation then You must: (a) comply with +the terms of this License to the maximum extent possible; and (b) +describe the limitations and the code they affect. Such description must +be placed in a text file included with all distributions of the Covered +Software under this License. Except to the extent prohibited by statute +or regulation, such description must be sufficiently detailed for a +recipient of ordinary skill to be able to understand it. + +5. Termination +-------------- + +5.1. The rights granted under this License will terminate automatically +if You fail to comply with any of its terms. However, if You become +compliant, then the rights granted under this License from a particular +Contributor are reinstated (a) provisionally, unless and until such +Contributor explicitly and finally terminates Your grants, and (b) on an +ongoing basis, if such Contributor fails to notify You of the +non-compliance by some reasonable means prior to 60 days after You have +come back into compliance. Moreover, Your grants from a particular +Contributor are reinstated on an ongoing basis if such Contributor +notifies You of the non-compliance by some reasonable means, this is the +first time You have received notice of non-compliance with this License +from such Contributor, and You become compliant prior to 30 days after +Your receipt of the notice. + +5.2. If You initiate litigation against any entity by asserting a patent +infringement claim (excluding declaratory judgment actions, +counter-claims, and cross-claims) alleging that a Contributor Version +directly or indirectly infringes any patent, then the rights granted to +You by any and all Contributors for the Covered Software under Section +2.1 of this License shall terminate. + +5.3. In the event of termination under Sections 5.1 or 5.2 above, all +end user license agreements (excluding distributors and resellers) which +have been validly granted by You or Your distributors under this License +prior to termination shall survive termination. + +************************************************************************ +* * +* 6. Disclaimer of Warranty * +* ------------------------- * +* * +* Covered Software is provided under this License on an "as is" * +* basis, without warranty of any kind, either expressed, implied, or * +* statutory, including, without limitation, warranties that the * +* Covered Software is free of defects, merchantable, fit for a * +* particular purpose or non-infringing. The entire risk as to the * +* quality and performance of the Covered Software is with You. * +* Should any Covered Software prove defective in any respect, You * +* (not any Contributor) assume the cost of any necessary servicing, * +* repair, or correction. This disclaimer of warranty constitutes an * +* essential part of this License. No use of any Covered Software is * +* authorized under this License except under this disclaimer. * +* * +************************************************************************ + +************************************************************************ +* * +* 7. Limitation of Liability * +* -------------------------- * +* * +* Under no circumstances and under no legal theory, whether tort * +* (including negligence), contract, or otherwise, shall any * +* Contributor, or anyone who distributes Covered Software as * +* permitted above, be liable to You for any direct, indirect, * +* special, incidental, or consequential damages of any character * +* including, without limitation, damages for lost profits, loss of * +* goodwill, work stoppage, computer failure or malfunction, or any * +* and all other commercial damages or losses, even if such party * +* shall have been informed of the possibility of such damages. This * +* limitation of liability shall not apply to liability for death or * +* personal injury resulting from such party's negligence to the * +* extent applicable law prohibits such limitation. Some * +* jurisdictions do not allow the exclusion or limitation of * +* incidental or consequential damages, so this exclusion and * +* limitation may not apply to You. * +* * +************************************************************************ + +8. Litigation +------------- + +Any litigation relating to this License may be brought only in the +courts of a jurisdiction where the defendant maintains its principal +place of business and such litigation shall be governed by laws of that +jurisdiction, without reference to its conflict-of-law provisions. +Nothing in this Section shall prevent a party's ability to bring +cross-claims or counter-claims. + +9. Miscellaneous +---------------- + +This License represents the complete agreement concerning the subject +matter hereof. If any provision of this License is held to be +unenforceable, such provision shall be reformed only to the extent +necessary to make it enforceable. Any law or regulation which provides +that the language of a contract shall be construed against the drafter +shall not be used to construe this License against a Contributor. + +10. Versions of the License +--------------------------- + +10.1. New Versions + +Mozilla Foundation is the license steward. Except as provided in Section +10.3, no one other than the license steward has the right to modify or +publish new versions of this License. Each version will be given a +distinguishing version number. + +10.2. Effect of New Versions + +You may distribute the Covered Software under the terms of the version +of the License under which You originally received the Covered Software, +or under the terms of any subsequent version published by the license +steward. + +10.3. Modified Versions + +If you create software not governed by this License, and you want to +create a new license for such software, you may create and use a +modified version of this License if you rename the license and remove +any references to the name of the license steward (except to note that +such modified license differs from this License). + +10.4. Distributing Source Code Form that is Incompatible With Secondary +Licenses + +If You choose to distribute Source Code Form that is Incompatible With +Secondary Licenses under the terms of this version of the License, the +notice described in Exhibit B of this License must be attached. + +Exhibit A - Source Code Form License Notice +------------------------------------------- + + This Source Code Form is subject to the terms of the Mozilla Public + License, v. 2.0. If a copy of the MPL was not distributed with this + file, You can obtain one at http://mozilla.org/MPL/2.0/. + +If it is not possible or desirable to put the notice in a particular +file, then You may include the notice in a location (such as a LICENSE +file in a relevant directory) where a recipient would be likely to look +for such a notice. + +You may add additional accurate notices of copyright ownership. + +Exhibit B - "Incompatible With Secondary Licenses" Notice +--------------------------------------------------------- + + This Source Code Form is "Incompatible With Secondary Licenses", as + defined by the Mozilla Public License, v. 2.0. diff --git a/METADATA b/METADATA new file mode 100644 index 0000000..131ba68 --- /dev/null +++ b/METADATA @@ -0,0 +1,20 @@ +name: "uniffi_core" +description: "a multi-language bindings generator for rust (runtime support code)" +third_party { + identifier { + type: "crates.io" + value: "uniffi_core" + } + identifier { + type: "Archive" + value: "https://static.crates.io/crates/uniffi_core/uniffi_core-0.26.1.crate" + primary_source: true + } + version: "0.26.1" + license_type: RECIPROCAL + last_upgrade_date { + year: 2024 + month: 4 + day: 10 + } +} diff --git a/MODULE_LICENSE_MPL b/MODULE_LICENSE_MPL new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/MODULE_LICENSE_MPL @@ -0,0 +1,2 @@ +# Bug component: 688011 +include platform/prebuilts/rust:main:/OWNERS diff --git a/README.md b/README.md new file mode 100644 index 0000000..bb72360 --- /dev/null +++ b/README.md @@ -0,0 +1,79 @@ +# UniFFI - a multi-language bindings generator for Rust + +UniFFI is a toolkit for building cross-platform software components in Rust. + +For the impatient, see [**the UniFFI user guide**](https://mozilla.github.io/uniffi-rs/) +or [**the UniFFI examples**](https://github.com/mozilla/uniffi-rs/tree/main/examples#example-uniffi-components). + +By writing your core business logic in Rust and describing its interface in an "object model", +you can use UniFFI to help you: + +* Compile your Rust code into a shared library for use on different target platforms. +* Generate bindings to load and use the library from different target languages. + +You can describe your object model in an [interface definition file](https://mozilla.github.io/uniffi-rs/udl_file_spec.html) +or [by using proc-macros](https://mozilla.github.io/uniffi-rs/proc_macro/index.html). + +UniFFI is currently extensively by Mozilla in Firefox mobile and desktop browsers; +written once in Rust, auto-generated bindings allow that functionality to be called +from both Kotlin (for Android apps) and Swift (for iOS apps). +It also has a growing community of users shipping various cool things to many users. + +UniFII comes with support for **Kotlin**, **Swift**, **Python** and **Ruby** with 3rd party bindings available for **C#** and **Golang**. +Additional foreign language bindings can be developed externally and we welcome contributions to list them here. +See [Third-party foreign language bindings](#third-party-foreign-language-bindings). + +## User Guide + +You can read more about using the tool in [**the UniFFI user guide**](https://mozilla.github.io/uniffi-rs/). + +We consider it ready for production use, but UniFFI is a long way from a 1.0 release with lots of internal work still going on. +We try hard to avoid breaking simple consumers, but more advanced things might break as you upgrade over time. + +### Etymology and Pronunciation + +ˈjuːnɪfaɪ. Pronounced to rhyme with "unify". + +A portmanteau word that also puns with "unify", to signify the joining of one codebase accessed from many languages. + +uni - [Latin ūni-, from ūnus, one] +FFI - [Abbreviation, Foreign Function Interface] + +## Alternative tools + +Other tools we know of which try and solve a similarly shaped problem are: + +* [Diplomat](https://github.com/rust-diplomat/diplomat/) - see our [writeup of + the different approach taken by that tool](docs/diplomat-and-macros.md) +* [Interoptopus](https://github.com/ralfbiedert/interoptopus/) + +(Please open a PR if you think other tools should be listed!) + +## Third-party foreign language bindings + +* [Kotlin Multiplatform support](https://gitlab.com/trixnity/uniffi-kotlin-multiplatform-bindings). The repository contains Kotlin Multiplatform bindings generation for UniFFI, letting you target both JVM and Native. +* [Go bindings](https://github.com/NordSecurity/uniffi-bindgen-go) +* [C# bindings](https://github.com/NordSecurity/uniffi-bindgen-cs) +* [Dart bindings](https://github.com/NiallBunting/uniffi-rs-dart) + +### External resources + +There are a few third-party resources that make it easier to work with UniFFI: + +* [Plugin support for `.udl` files](https://github.com/Lonami/uniffi-dl) for the IDEA platform ([*uniffi-dl* in the JetBrains marketplace](https://plugins.jetbrains.com/plugin/20527-uniffi-dl)). It provides syntax highlighting, code folding, code completion, reference resolution and navigation (among others features) for the [UniFFI Definition Language (UDL)](https://mozilla.github.io/uniffi-rs/). +* [cargo swift](https://github.com/antoniusnaumann/cargo-swift), a cargo plugin to build a Swift Package from Rust code. It provides an init command for setting up a UniFFI crate and a package command for building a Swift package from Rust code - without the need for additional configuration or build scripts. + +(Please open a PR if you think other resources should be listed!) + +## Contributing + +If this tool sounds interesting to you, please help us develop it! You can: + +* View the [contributor guidelines](./docs/contributing.md). +* File or work on [issues](https://github.com/mozilla/uniffi-rs/issues) here in GitHub. +* Join discussions in the [#uniffi:mozilla.org](https://matrix.to/#/#uniffi:mozilla.org) + room on Matrix. + +## Code of Conduct + +This project is governed by Mozilla's [Community Participation Guidelines](./CODE_OF_CONDUCT.md). diff --git a/cargo_embargo.json b/cargo_embargo.json new file mode 100644 index 0000000..9a0a579 --- /dev/null +++ b/cargo_embargo.json @@ -0,0 +1,3 @@ +{ + "tests": true +} diff --git a/release.toml b/release.toml new file mode 100644 index 0000000..2ff9c83 --- /dev/null +++ b/release.toml @@ -0,0 +1,15 @@ +# Note that this `release.toml` exists to capture things that must only be +# done once for `cargo release-backend-crates`. +# +# [../uniffi/release.toml](../uniffi/release.toml) captures things that must only be done for `cargo release-uniffi` +# +# All other config exists in [../release.toml](../release.toml). + +tag = false + +# This is how we manage the sections in CHANGELOG.md +pre-release-replacements = [ + {file="../CHANGELOG.md", search="\\[\\[UnreleasedBackendVersion\\]\\]", replace="v{{version}}", exactly=1}, + {file="../CHANGELOG.md", search="\\[\\[ReleaseDate\\]\\]", replace="{{date}}", exactly=1}, + {file="../CHANGELOG.md", search="<!-- next-header -->", replace="<!-- next-header -->\n\n## [[NextUnreleasedUniFFIVersion]] (backend crates: [[UnreleasedBackendVersion]]) - (_[[ReleaseDate]]_)\n\n[All changes in [[NextUnreleasedUniFFIVersion]]](https://github.com/mozilla/uniffi-rs/compare/v{{version}}...NEXT_HEAD).", exactly=1}, +] diff --git a/src/ffi/callbackinterface.rs b/src/ffi/callbackinterface.rs new file mode 100644 index 0000000..41c85dc --- /dev/null +++ b/src/ffi/callbackinterface.rs @@ -0,0 +1,309 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +//! Callback interfaces are traits specified in UDL which can be implemented by foreign languages. +//! +//! # Using callback interfaces +//! +//! 1. Define a Rust trait. +//! +//! This toy example defines a way of Rust accessing a key-value store exposed +//! by the host operating system (e.g. the key chain). +//! +//! ``` +//! trait Keychain: Send { +//! fn get(&self, key: String) -> Option<String>; +//! fn put(&self, key: String, value: String); +//! } +//! ``` +//! +//! 2. Define a callback interface in the UDL +//! +//! ```idl +//! callback interface Keychain { +//! string? get(string key); +//! void put(string key, string data); +//! }; +//! ``` +//! +//! 3. And allow it to be passed into Rust. +//! +//! Here, we define a constructor to pass the keychain to rust, and then another method +//! which may use it. +//! +//! In UDL: +//! ```idl +//! object Authenticator { +//! constructor(Keychain keychain); +//! void login(); +//! } +//! ``` +//! +//! In Rust: +//! +//! ``` +//!# trait Keychain: Send { +//!# fn get(&self, key: String) -> Option<String>; +//!# fn put(&self, key: String, value: String); +//!# } +//! struct Authenticator { +//! keychain: Box<dyn Keychain>, +//! } +//! +//! impl Authenticator { +//! pub fn new(keychain: Box<dyn Keychain>) -> Self { +//! Self { keychain } +//! } +//! pub fn login(&self) { +//! let username = self.keychain.get("username".into()); +//! let password = self.keychain.get("password".into()); +//! } +//! } +//! ``` +//! 4. Create an foreign language implementation of the callback interface. +//! +//! In this example, here's a Kotlin implementation. +//! +//! ```kotlin +//! class AndroidKeychain: Keychain { +//! override fun get(key: String): String? { +//! // … elide the implementation. +//! return value +//! } +//! override fun put(key: String) { +//! // … elide the implementation. +//! } +//! } +//! ``` +//! 5. Pass the implementation to Rust. +//! +//! Again, in Kotlin +//! +//! ```kotlin +//! val authenticator = Authenticator(AndroidKeychain()) +//! authenticator.login() +//! ``` +//! +//! # How it works. +//! +//! ## High level +//! +//! Uniffi generates a protocol or interface in client code in the foreign language must implement. +//! +//! For each callback interface, a `CallbackInternals` (on the Foreign Language side) and `ForeignCallbackInternals` +//! (on Rust side) manages the process through a `ForeignCallback`. There is one `ForeignCallback` per callback interface. +//! +//! Passing a callback interface implementation from foreign language (e.g. `AndroidKeychain`) into Rust causes the +//! `KeychainCallbackInternals` to store the instance in a handlemap. +//! +//! The object handle is passed over to Rust, and used to instantiate a struct `KeychainProxy` which implements +//! the trait. This proxy implementation is generate by Uniffi. The `KeychainProxy` object is then passed to +//! client code as `Box<dyn Keychain>`. +//! +//! Methods on `KeychainProxy` objects (e.g. `self.keychain.get("username".into())`) encode the arguments into a `RustBuffer`. +//! Using the `ForeignCallback`, it calls the `CallbackInternals` object on the foreign language side using the +//! object handle, and the method selector. +//! +//! The `CallbackInternals` object unpacks the arguments from the passed buffer, gets the object out from the handlemap, +//! and calls the actual implementation of the method. +//! +//! If there's a return value, it is packed up in to another `RustBuffer` and used as the return value for +//! `ForeignCallback`. The caller of `ForeignCallback`, the `KeychainProxy` unpacks the returned buffer into the correct +//! type and then returns to client code. +//! + +use crate::{ForeignCallback, ForeignCallbackCell, Lift, LiftReturn, RustBuffer}; +use std::fmt; + +/// The method index used by the Drop trait to communicate to the foreign language side that Rust has finished with it, +/// and it can be deleted from the handle map. +pub const IDX_CALLBACK_FREE: u32 = 0; + +/// Result of a foreign callback invocation +#[repr(i32)] +#[derive(Debug, PartialEq, Eq)] +pub enum CallbackResult { + /// Successful call. + /// The return value is serialized to `buf_ptr`. + Success = 0, + /// Expected error. + /// This is returned when a foreign method throws an exception that corresponds to the Rust Err half of a Result. + /// The error value is serialized to `buf_ptr`. + Error = 1, + /// Unexpected error. + /// An error message string is serialized to `buf_ptr`. + UnexpectedError = 2, +} + +impl TryFrom<i32> for CallbackResult { + // On errors we return the unconverted value + type Error = i32; + + fn try_from(value: i32) -> Result<Self, i32> { + match value { + 0 => Ok(Self::Success), + 1 => Ok(Self::Error), + 2 => Ok(Self::UnexpectedError), + n => Err(n), + } + } +} + +/// Struct to hold a foreign callback. +pub struct ForeignCallbackInternals { + callback_cell: ForeignCallbackCell, +} + +impl ForeignCallbackInternals { + pub const fn new() -> Self { + ForeignCallbackInternals { + callback_cell: ForeignCallbackCell::new(), + } + } + + pub fn set_callback(&self, callback: ForeignCallback) { + self.callback_cell.set(callback); + } + + /// Invoke a callback interface method on the foreign side and return the result + pub fn invoke_callback<R, UniFfiTag>(&self, handle: u64, method: u32, args: RustBuffer) -> R + where + R: LiftReturn<UniFfiTag>, + { + let mut ret_rbuf = RustBuffer::new(); + let callback = self.callback_cell.get(); + let raw_result = unsafe { + callback( + handle, + method, + args.data_pointer(), + args.len() as i32, + &mut ret_rbuf, + ) + }; + RustBuffer::destroy(args); + let result = CallbackResult::try_from(raw_result) + .unwrap_or_else(|code| panic!("Callback failed with unexpected return code: {code}")); + match result { + CallbackResult::Success => R::lift_callback_return(ret_rbuf), + CallbackResult::Error => R::lift_callback_error(ret_rbuf), + CallbackResult::UnexpectedError => { + let reason = if !ret_rbuf.is_empty() { + match <String as Lift<UniFfiTag>>::try_lift(ret_rbuf) { + Ok(s) => s, + Err(e) => { + log::error!("{{ trait_name }} Error reading ret_buf: {e}"); + String::from("[Error reading reason]") + } + } + } else { + RustBuffer::destroy(ret_rbuf); + String::from("[Unknown Reason]") + }; + R::handle_callback_unexpected_error(UnexpectedUniFFICallbackError { reason }) + } + } + } +} + +/// Used when internal/unexpected error happened when calling a foreign callback, for example when +/// a unknown exception is raised +/// +/// User callback error types must implement a From impl from this type to their own error type. +#[derive(Debug)] +pub struct UnexpectedUniFFICallbackError { + pub reason: String, +} + +impl UnexpectedUniFFICallbackError { + pub fn from_reason(reason: String) -> Self { + Self { reason } + } +} + +impl fmt::Display for UnexpectedUniFFICallbackError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!( + f, + "UnexpectedUniFFICallbackError(reason: {:?})", + self.reason + ) + } +} + +impl std::error::Error for UnexpectedUniFFICallbackError {} + +// Autoref-based specialization for converting UnexpectedUniFFICallbackError into error types. +// +// For more details, see: +// https://github.com/dtolnay/case-studies/blob/master/autoref-specialization/README.md + +// Define two ZST types: +// - One implements `try_convert_unexpected_callback_error` by always returning an error value. +// - The specialized version implements it using `From<UnexpectedUniFFICallbackError>` + +#[doc(hidden)] +#[derive(Debug)] +pub struct UnexpectedUniFFICallbackErrorConverterGeneric; + +impl UnexpectedUniFFICallbackErrorConverterGeneric { + pub fn try_convert_unexpected_callback_error<E>( + &self, + e: UnexpectedUniFFICallbackError, + ) -> anyhow::Result<E> { + Err(e.into()) + } +} + +#[doc(hidden)] +#[derive(Debug)] +pub struct UnexpectedUniFFICallbackErrorConverterSpecialized; + +impl UnexpectedUniFFICallbackErrorConverterSpecialized { + pub fn try_convert_unexpected_callback_error<E>( + &self, + e: UnexpectedUniFFICallbackError, + ) -> anyhow::Result<E> + where + E: From<UnexpectedUniFFICallbackError>, + { + Ok(E::from(e)) + } +} + +// Macro to convert an UnexpectedUniFFICallbackError value for a particular type. This is used in +// the `ConvertError` implementation. +#[doc(hidden)] +#[macro_export] +macro_rules! convert_unexpected_error { + ($error:ident, $ty:ty) => {{ + // Trait for generic conversion, implemented for all &T. + pub trait GetConverterGeneric { + fn get_converter(&self) -> $crate::UnexpectedUniFFICallbackErrorConverterGeneric; + } + + impl<T> GetConverterGeneric for &T { + fn get_converter(&self) -> $crate::UnexpectedUniFFICallbackErrorConverterGeneric { + $crate::UnexpectedUniFFICallbackErrorConverterGeneric + } + } + // Trait for specialized conversion, implemented for all T that implements + // `Into<ErrorType>`. I.e. it's implemented for UnexpectedUniFFICallbackError when + // ErrorType implements From<UnexpectedUniFFICallbackError>. + pub trait GetConverterSpecialized { + fn get_converter(&self) -> $crate::UnexpectedUniFFICallbackErrorConverterSpecialized; + } + + impl<T: Into<$ty>> GetConverterSpecialized for T { + fn get_converter(&self) -> $crate::UnexpectedUniFFICallbackErrorConverterSpecialized { + $crate::UnexpectedUniFFICallbackErrorConverterSpecialized + } + } + // Here's the hack. Because of the auto-ref rules, this will use `GetConverterSpecialized` + // if it's implemented and `GetConverterGeneric` if not. + (&$error) + .get_converter() + .try_convert_unexpected_callback_error($error) + }}; +} diff --git a/src/ffi/ffidefault.rs b/src/ffi/ffidefault.rs new file mode 100644 index 0000000..d3ca943 --- /dev/null +++ b/src/ffi/ffidefault.rs @@ -0,0 +1,58 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +//! FfiDefault trait +//! +//! When we make a FFI call into Rust we always need to return a value, even if that value will be +//! ignored because we're flagging an exception. This trait defines what that value is for our +//! supported FFI types. + +use paste::paste; + +pub trait FfiDefault { + fn ffi_default() -> Self; +} + +// Most types can be handled by delegating to Default +macro_rules! impl_ffi_default_with_default { + ($($T:ty,)+) => { impl_ffi_default_with_default!($($T),+); }; + ($($T:ty),*) => { + $( + paste! { + impl FfiDefault for $T { + fn ffi_default() -> Self { + $T::default() + } + } + } + )* + }; +} + +impl_ffi_default_with_default! { + bool, i8, u8, i16, u16, i32, u32, i64, u64, f32, f64 +} + +// Implement FfiDefault for the remaining types +impl FfiDefault for () { + fn ffi_default() {} +} + +impl FfiDefault for *const std::ffi::c_void { + fn ffi_default() -> Self { + std::ptr::null() + } +} + +impl FfiDefault for crate::RustBuffer { + fn ffi_default() -> Self { + unsafe { Self::from_raw_parts(std::ptr::null_mut(), 0, 0) } + } +} + +impl<T> FfiDefault for Option<T> { + fn ffi_default() -> Self { + None + } +} diff --git a/src/ffi/foreignbytes.rs b/src/ffi/foreignbytes.rs new file mode 100644 index 0000000..9516f61 --- /dev/null +++ b/src/ffi/foreignbytes.rs @@ -0,0 +1,118 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/// Support for reading a slice of foreign-language-allocated bytes over the FFI. +/// +/// Foreign language code can pass a slice of bytes by providing a data pointer +/// and length, and this struct provides a convenient wrapper for working with +/// that pair. Naturally, this can be tremendously unsafe! So here are the details: +/// +/// * The foreign language code must ensure the provided buffer stays alive +/// and unchanged for the duration of the call to which the `ForeignBytes` +/// struct was provided. +/// +/// To work with the bytes in Rust code, use `as_slice()` to view the data +/// as a `&[u8]`. +/// +/// Implementation note: all the fields of this struct are private and it has no +/// constructors, so consuming crates cant create instances of it. If you've +/// got a `ForeignBytes`, then you received it over the FFI and are assuming that +/// the foreign language code is upholding the above invariants. +/// +/// This struct is based on `ByteBuffer` from the `ffi-support` crate, but modified +/// to give a read-only view of externally-provided bytes. +#[repr(C)] +pub struct ForeignBytes { + /// The length of the pointed-to data. + /// We use an `i32` for compatibility with JNA. + len: i32, + /// The pointer to the foreign-owned bytes. + data: *const u8, +} + +impl ForeignBytes { + /// Creates a `ForeignBytes` from its constituent fields. + /// + /// This is intended mainly as an internal convenience function and should not + /// be used outside of this module. + /// + /// # Safety + /// + /// You must ensure that the raw parts uphold the documented invariants of this class. + pub unsafe fn from_raw_parts(data: *const u8, len: i32) -> Self { + Self { len, data } + } + + /// View the foreign bytes as a `&[u8]`. + /// + /// # Panics + /// + /// Panics if the provided struct has a null pointer but non-zero length. + /// Panics if the provided length is negative. + pub fn as_slice(&self) -> &[u8] { + if self.data.is_null() { + assert!(self.len == 0, "null ForeignBytes had non-zero length"); + &[] + } else { + unsafe { std::slice::from_raw_parts(self.data, self.len()) } + } + } + + /// Get the length of this slice of bytes. + /// + /// # Panics + /// + /// Panics if the provided length is negative. + pub fn len(&self) -> usize { + self.len + .try_into() + .expect("bytes length negative or overflowed") + } + + /// Returns true if the length of this slice of bytes is 0. + pub fn is_empty(&self) -> bool { + self.len == 0 + } +} + +#[cfg(test)] +mod test { + use super::*; + #[test] + fn test_foreignbytes_access() { + let v = [1u8, 2, 3]; + let fbuf = unsafe { ForeignBytes::from_raw_parts(v.as_ptr(), 3) }; + assert_eq!(fbuf.len(), 3); + assert_eq!(fbuf.as_slice(), &[1u8, 2, 3]); + } + + #[test] + fn test_foreignbytes_empty() { + let v = Vec::<u8>::new(); + let fbuf = unsafe { ForeignBytes::from_raw_parts(v.as_ptr(), 0) }; + assert_eq!(fbuf.len(), 0); + assert_eq!(fbuf.as_slice(), &[0u8; 0]); + } + + #[test] + fn test_foreignbytes_null_means_empty() { + let fbuf = unsafe { ForeignBytes::from_raw_parts(std::ptr::null_mut(), 0) }; + assert_eq!(fbuf.as_slice(), &[0u8; 0]); + } + + #[test] + #[should_panic] + fn test_foreignbytes_null_must_have_zero_length() { + let fbuf = unsafe { ForeignBytes::from_raw_parts(std::ptr::null_mut(), 12) }; + fbuf.as_slice(); + } + + #[test] + #[should_panic] + fn test_foreignbytes_provided_len_must_be_non_negative() { + let v = [0u8, 1, 2]; + let fbuf = unsafe { ForeignBytes::from_raw_parts(v.as_ptr(), -1) }; + fbuf.as_slice(); + } +} diff --git a/src/ffi/foreigncallbacks.rs b/src/ffi/foreigncallbacks.rs new file mode 100644 index 0000000..68d9a0d --- /dev/null +++ b/src/ffi/foreigncallbacks.rs @@ -0,0 +1,80 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +//! This module contains code to handle foreign callbacks - C-ABI functions that are defined by a +//! foreign language, then registered with UniFFI. These callbacks are used to implement callback +//! interfaces, async scheduling etc. Foreign callbacks are registered at startup, when the foreign +//! code loads the exported library. For each callback type, we also define a "cell" type for +//! storing the callback. + +use std::sync::atomic::{AtomicUsize, Ordering}; + +use crate::RustBuffer; + +/// ForeignCallback is the Rust representation of a foreign language function. +/// It is the basis for all callbacks interfaces. It is registered exactly once per callback interface, +/// at library start up time. +/// Calling this method is only done by generated objects which mirror callback interfaces objects in the foreign language. +/// +/// * The `handle` is the key into a handle map on the other side of the FFI used to look up the foreign language object +/// that implements the callback interface/trait. +/// * The `method` selector specifies the method that will be called on the object, by looking it up in a list of methods from +/// the IDL. The list is 1 indexed. Note that the list of methods is generated by UniFFI from the IDL and used in all +/// bindings, so we can rely on the method list being stable within the same run of UniFFI. +/// * `args_data` and `args_len` represents a serialized buffer of arguments to the function. The scaffolding code +/// writes the callback arguments to this buffer, in order, using `FfiConverter.write()`. The bindings code reads the +/// arguments from the buffer and passes them to the user's callback. +/// * `buf_ptr` is a pointer to where the resulting buffer will be written. UniFFI will allocate a +/// buffer to write the result into. +/// * Callbacks return one of the `CallbackResult` values +/// Note: The output buffer might still contain 0 bytes of data. +pub type ForeignCallback = unsafe extern "C" fn( + handle: u64, + method: u32, + args_data: *const u8, + args_len: i32, + buf_ptr: *mut RustBuffer, +) -> i32; + +/// Store a [ForeignCallback] pointer +pub(crate) struct ForeignCallbackCell(AtomicUsize); + +/// Macro to define foreign callback types as well as the callback cell. +macro_rules! impl_foreign_callback_cell { + ($callback_type:ident, $cell_type:ident) => { + // Overly-paranoid sanity checking to ensure that these types are + // convertible between each-other. `transmute` actually should check this for + // us too, but this helps document the invariants we rely on in this code. + // + // Note that these are guaranteed by + // https://rust-lang.github.io/unsafe-code-guidelines/layout/function-pointers.html + // and thus this is a little paranoid. + static_assertions::assert_eq_size!(usize, $callback_type); + static_assertions::assert_eq_size!(usize, Option<$callback_type>); + + impl $cell_type { + pub const fn new() -> Self { + Self(AtomicUsize::new(0)) + } + + pub fn set(&self, callback: $callback_type) { + // Store the pointer using Ordering::Relaxed. This is sufficient since callback + // should be set at startup, before there's any chance of using them. + self.0.store(callback as usize, Ordering::Relaxed); + } + + pub fn get(&self) -> $callback_type { + let ptr_value = self.0.load(Ordering::Relaxed); + unsafe { + // SAFETY: self.0 was set in `set` from our function pointer type, so + // it's safe to transmute it back here. + ::std::mem::transmute::<usize, Option<$callback_type>>(ptr_value) + .expect("Bug: callback not set. This is likely a uniffi bug.") + } + } + } + }; +} + +impl_foreign_callback_cell!(ForeignCallback, ForeignCallbackCell); diff --git a/src/ffi/mod.rs b/src/ffi/mod.rs new file mode 100644 index 0000000..24b9bba --- /dev/null +++ b/src/ffi/mod.rs @@ -0,0 +1,21 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +//! Types that can cross the FFI boundary. + +pub mod callbackinterface; +pub mod ffidefault; +pub mod foreignbytes; +pub mod foreigncallbacks; +pub mod rustbuffer; +pub mod rustcalls; +pub mod rustfuture; + +pub use callbackinterface::*; +pub use ffidefault::FfiDefault; +pub use foreignbytes::*; +pub use foreigncallbacks::*; +pub use rustbuffer::*; +pub use rustcalls::*; +pub use rustfuture::*; diff --git a/src/ffi/rustbuffer.rs b/src/ffi/rustbuffer.rs new file mode 100644 index 0000000..cfcc542 --- /dev/null +++ b/src/ffi/rustbuffer.rs @@ -0,0 +1,355 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +use crate::ffi::{rust_call, ForeignBytes, RustCallStatus}; + +/// Support for passing an allocated-by-Rust buffer of bytes over the FFI. +/// +/// We can pass a `Vec<u8>` to foreign language code by decomposing it into +/// its raw parts (buffer pointer, length, and capacity) and passing those +/// around as a struct. Naturally, this can be tremendously unsafe! So here +/// are the details: +/// +/// * `RustBuffer` structs must only ever be constructed from a `Vec<u8>`, +/// either explicitly via `RustBuffer::from_vec` or indirectly by calling +/// one of the `RustBuffer::new*` constructors. +/// +/// * `RustBuffer` structs do not implement `Drop`, since they are intended +/// to be passed to foreign-language code outside of the control of Rust's +/// ownership system. To avoid memory leaks they *must* passed back into +/// Rust and either explicitly destroyed using `RustBuffer::destroy`, or +/// converted back to a `Vec<u8>` using `RustBuffer::destroy_into_vec` +/// (which will then be dropped via Rust's usual ownership-tracking system). +/// +/// Foreign-language code should not construct `RustBuffer` structs other than +/// by receiving them from a call into the Rust code, and should not modify them +/// apart from the following safe operations: +/// +/// * Writing bytes into the buffer pointed to by `data`, without writing +/// beyond the indicated `capacity`. +/// +/// * Adjusting the `len` property to indicate the amount of data written, +/// while ensuring that 0 <= `len` <= `capacity`. +/// +/// * As a special case, constructing a `RustBuffer` with zero capacity, zero +/// length, and a null `data` pointer to indicate an empty buffer. +/// +/// In particular, it is not safe for foreign-language code to construct a `RustBuffer` +/// that points to its own allocated memory; use the `ForeignBytes` struct to +/// pass a view of foreign-owned memory in to Rust code. +/// +/// Implementation note: all the fields of this struct are private, so you can't +/// manually construct instances that don't come from a `Vec<u8>`. If you've got +/// a `RustBuffer` then it either came from a public constructor (all of which +/// are safe) or it came from foreign-language code (which should have in turn +/// received it by calling some Rust function, and should be respecting the +/// invariants listed above). +/// +/// This struct is based on `ByteBuffer` from the `ffi-support` crate, but modified +/// to retain unallocated capacity rather than truncating to the occupied length. +#[repr(C)] +#[derive(Debug)] +pub struct RustBuffer { + /// The allocated capacity of the underlying `Vec<u8>`. + /// In Rust this is a `usize`, but we use an `i32` for compatibility with JNA. + capacity: i32, + /// The occupied length of the underlying `Vec<u8>`. + /// In Rust this is a `usize`, but we use an `i32` for compatibility with JNA. + len: i32, + /// The pointer to the allocated buffer of the `Vec<u8>`. + data: *mut u8, +} + +// Mark `RustBuffer` as safe to send between threads, despite the `u8` pointer. The only mutable +// use of that pointer is in `destroy_into_vec()` which requires a &mut on the `RustBuffer`. This +// is required to send `RustBuffer` inside a `RustFuture` +unsafe impl Send for RustBuffer {} + +impl RustBuffer { + /// Creates an empty `RustBuffer`. + /// + /// The buffer will not allocate. + /// The resulting vector will not be automatically dropped; you must + /// arrange to call `destroy` or `destroy_into_vec` when finished with it. + pub fn new() -> Self { + Self::from_vec(Vec::new()) + } + + /// Creates a `RustBuffer` from its constituent fields. + /// + /// This is intended mainly as an internal convenience function and should not + /// be used outside of this module. + /// + /// # Safety + /// + /// You must ensure that the raw parts uphold the documented invariants of this class. + pub unsafe fn from_raw_parts(data: *mut u8, len: i32, capacity: i32) -> Self { + Self { + capacity, + len, + data, + } + } + + /// Get the current length of the buffer, as a `usize`. + /// + /// This is mostly a helper function to convert the `i32` length field + /// into a `usize`, which is what Rust code usually expects. + /// + /// # Panics + /// + /// Panics if called on an invalid struct obtained from foreign-language code, + /// in which the `len` field is negative. + pub fn len(&self) -> usize { + self.len + .try_into() + .expect("buffer length negative or overflowed") + } + + /// Get a pointer to the data + pub fn data_pointer(&self) -> *const u8 { + self.data + } + + /// Returns true if the length of the buffer is 0. + pub fn is_empty(&self) -> bool { + self.len == 0 + } + + /// Creates a `RustBuffer` zero-filed to the requested size. + /// + /// The resulting vector will not be automatically dropped; you must + /// arrange to call `destroy` or `destroy_into_vec` when finished with it. + /// + /// # Panics + /// + /// Panics if the requested size is too large to fit in an `i32`, and + /// hence would risk incompatibility with some foreign-language code. + pub fn new_with_size(size: usize) -> Self { + assert!( + size < i32::MAX as usize, + "RustBuffer requested size too large" + ); + Self::from_vec(vec![0u8; size]) + } + + /// Consumes a `Vec<u8>` and returns its raw parts as a `RustBuffer`. + /// + /// The resulting vector will not be automatically dropped; you must + /// arrange to call `destroy` or `destroy_into_vec` when finished with it. + /// + /// # Panics + /// + /// Panics if the vector's length or capacity are too large to fit in an `i32`, + /// and hence would risk incompatibility with some foreign-language code. + pub fn from_vec(v: Vec<u8>) -> Self { + let capacity = i32::try_from(v.capacity()).expect("buffer capacity cannot fit into a i32."); + let len = i32::try_from(v.len()).expect("buffer length cannot fit into a i32."); + let mut v = std::mem::ManuallyDrop::new(v); + unsafe { Self::from_raw_parts(v.as_mut_ptr(), len, capacity) } + } + + /// Converts this `RustBuffer` back into an owned `Vec<u8>`. + /// + /// This restores ownership of the underlying buffer to Rust, meaning it will + /// be dropped when the `Vec<u8>` is dropped. The `RustBuffer` *must* have been + /// previously obtained from a valid `Vec<u8>` owned by this Rust code. + /// + /// # Panics + /// + /// Panics if called on an invalid struct obtained from foreign-language code, + /// which does not respect the invairiants on `len` and `capacity`. + pub fn destroy_into_vec(self) -> Vec<u8> { + // Rust will never give us a null `data` pointer for a `Vec`, but + // foreign-language code can use it to cheaply pass an empty buffer. + if self.data.is_null() { + assert!(self.capacity == 0, "null RustBuffer had non-zero capacity"); + assert!(self.len == 0, "null RustBuffer had non-zero length"); + vec![] + } else { + let capacity: usize = self + .capacity + .try_into() + .expect("buffer capacity negative or overflowed"); + let len: usize = self + .len + .try_into() + .expect("buffer length negative or overflowed"); + assert!(len <= capacity, "RustBuffer length exceeds capacity"); + unsafe { Vec::from_raw_parts(self.data, len, capacity) } + } + } + + /// Reclaim memory stored in this `RustBuffer`. + /// + /// # Panics + /// + /// Panics if called on an invalid struct obtained from foreign-language code, + /// which does not respect the invairiants on `len` and `capacity`. + pub fn destroy(self) { + drop(self.destroy_into_vec()); + } +} + +impl Default for RustBuffer { + fn default() -> Self { + Self::new() + } +} + +// Functions for the RustBuffer functionality. +// +// The scaffolding code re-exports these functions, prefixed with the component name and UDL hash +// This creates a separate set of functions for each UniFFIed component, which is needed in the +// case where we create multiple dylib artifacts since each dylib will have its own allocator. + +/// This helper allocates a new byte buffer owned by the Rust code, and returns it +/// to the foreign-language code as a `RustBuffer` struct. Callers must eventually +/// free the resulting buffer, either by explicitly calling [`uniffi_rustbuffer_free`] defined +/// below, or by passing ownership of the buffer back into Rust code. +pub fn uniffi_rustbuffer_alloc(size: i32, call_status: &mut RustCallStatus) -> RustBuffer { + rust_call(call_status, || { + Ok(RustBuffer::new_with_size(size.max(0) as usize)) + }) +} + +/// This helper copies bytes owned by the foreign-language code into a new byte buffer owned +/// by the Rust code, and returns it as a `RustBuffer` struct. Callers must eventually +/// free the resulting buffer, either by explicitly calling the destructor defined below, +/// or by passing ownership of the buffer back into Rust code. +/// +/// # Safety +/// This function will dereference a provided pointer in order to copy bytes from it, so +/// make sure the `ForeignBytes` struct contains a valid pointer and length. +pub fn uniffi_rustbuffer_from_bytes( + bytes: ForeignBytes, + call_status: &mut RustCallStatus, +) -> RustBuffer { + rust_call(call_status, || { + let bytes = bytes.as_slice(); + Ok(RustBuffer::from_vec(bytes.to_vec())) + }) +} + +/// Free a byte buffer that had previously been passed to the foreign language code. +/// +/// # Safety +/// The argument *must* be a uniquely-owned `RustBuffer` previously obtained from a call +/// into the Rust code that returned a buffer, or you'll risk freeing unowned memory or +/// corrupting the allocator state. +pub fn uniffi_rustbuffer_free(buf: RustBuffer, call_status: &mut RustCallStatus) { + rust_call(call_status, || { + RustBuffer::destroy(buf); + Ok(()) + }) +} + +/// Reserve additional capacity in a byte buffer that had previously been passed to the +/// foreign language code. +/// +/// The first argument *must* be a uniquely-owned `RustBuffer` previously +/// obtained from a call into the Rust code that returned a buffer. Its underlying data pointer +/// will be reallocated if necessary and returned in a new `RustBuffer` struct. +/// +/// The second argument must be the minimum number of *additional* bytes to reserve +/// capacity for in the buffer; it is likely to reserve additional capacity in practice +/// due to amortized growth strategy of Rust vectors. +/// +/// # Safety +/// The first argument *must* be a uniquely-owned `RustBuffer` previously obtained from a call +/// into the Rust code that returned a buffer, or you'll risk freeing unowned memory or +/// corrupting the allocator state. +pub fn uniffi_rustbuffer_reserve( + buf: RustBuffer, + additional: i32, + call_status: &mut RustCallStatus, +) -> RustBuffer { + rust_call(call_status, || { + let additional: usize = additional + .try_into() + .expect("additional buffer length negative or overflowed"); + let mut v = buf.destroy_into_vec(); + v.reserve(additional); + Ok(RustBuffer::from_vec(v)) + }) +} + +#[cfg(test)] +mod test { + use super::*; + #[test] + fn test_rustbuffer_from_vec() { + let rbuf = RustBuffer::from_vec(vec![1u8, 2, 3]); + assert_eq!(rbuf.len(), 3); + assert_eq!(rbuf.destroy_into_vec(), vec![1u8, 2, 3]); + } + + #[test] + fn test_rustbuffer_empty() { + let rbuf = RustBuffer::new(); + assert_eq!(rbuf.len(), 0); + // Rust will never give us a null pointer, even for an empty buffer. + assert!(!rbuf.data.is_null()); + assert_eq!(rbuf.destroy_into_vec(), Vec::<u8>::new()); + } + + #[test] + fn test_rustbuffer_new_with_size() { + let rbuf = RustBuffer::new_with_size(5); + assert_eq!(rbuf.destroy_into_vec().as_slice(), &[0u8, 0, 0, 0, 0]); + + let rbuf = RustBuffer::new_with_size(0); + assert!(!rbuf.data.is_null()); + assert_eq!(rbuf.destroy_into_vec().as_slice(), &[0u8; 0]); + } + + #[test] + fn test_rustbuffer_null_means_empty() { + // This is how foreign-language code might cheaply indicate an empty buffer. + let rbuf = unsafe { RustBuffer::from_raw_parts(std::ptr::null_mut(), 0, 0) }; + assert_eq!(rbuf.destroy_into_vec().as_slice(), &[0u8; 0]); + } + + #[test] + #[should_panic] + fn test_rustbuffer_null_must_have_no_capacity() { + // We guard against foreign-language code providing this kind of invalid struct. + let rbuf = unsafe { RustBuffer::from_raw_parts(std::ptr::null_mut(), 0, 1) }; + rbuf.destroy_into_vec(); + } + #[test] + #[should_panic] + fn test_rustbuffer_null_must_have_zero_length() { + // We guard against foreign-language code providing this kind of invalid struct. + let rbuf = unsafe { RustBuffer::from_raw_parts(std::ptr::null_mut(), 12, 0) }; + rbuf.destroy_into_vec(); + } + + #[test] + #[should_panic] + fn test_rustbuffer_provided_capacity_must_be_non_negative() { + // We guard against foreign-language code providing this kind of invalid struct. + let mut v = vec![0u8, 1, 2]; + let rbuf = unsafe { RustBuffer::from_raw_parts(v.as_mut_ptr(), 3, -7) }; + rbuf.destroy_into_vec(); + } + + #[test] + #[should_panic] + fn test_rustbuffer_provided_len_must_be_non_negative() { + // We guard against foreign-language code providing this kind of invalid struct. + let mut v = vec![0u8, 1, 2]; + let rbuf = unsafe { RustBuffer::from_raw_parts(v.as_mut_ptr(), -1, 3) }; + rbuf.destroy_into_vec(); + } + + #[test] + #[should_panic] + fn test_rustbuffer_provided_len_must_not_exceed_capacity() { + // We guard against foreign-language code providing this kind of invalid struct. + let mut v = vec![0u8, 1, 2]; + let rbuf = unsafe { RustBuffer::from_raw_parts(v.as_mut_ptr(), 3, 2) }; + rbuf.destroy_into_vec(); + } +} diff --git a/src/ffi/rustcalls.rs b/src/ffi/rustcalls.rs new file mode 100644 index 0000000..51204ef --- /dev/null +++ b/src/ffi/rustcalls.rs @@ -0,0 +1,245 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +//! # Low-level support for calling rust functions +//! +//! This module helps the scaffolding code make calls to rust functions and pass back the result to the FFI bindings code. +//! +//! It handles: +//! - Catching panics +//! - Adapting the result of `Return::lower_return()` into either a return value or an +//! exception + +use crate::{FfiDefault, Lower, RustBuffer, UniFfiTag}; +use std::mem::MaybeUninit; +use std::panic; + +/// Represents the success/error of a rust call +/// +/// ## Usage +/// +/// - The consumer code creates a [RustCallStatus] with an empty [RustBuffer] and +/// [RustCallStatusCode::Success] (0) as the status code +/// - A pointer to this object is passed to the rust FFI function. This is an +/// "out parameter" which will be updated with any error that occurred during the function's +/// execution. +/// - After the call, if `code` is [RustCallStatusCode::Error] or [RustCallStatusCode::UnexpectedError] +/// then `error_buf` will be updated to contain a serialized error object. See +/// [RustCallStatusCode] for what gets serialized. The consumer is responsible for freeing `error_buf`. +/// +/// ## Layout/fields +/// +/// The layout of this struct is important since consumers on the other side of the FFI need to +/// construct it. If this were a C struct, it would look like: +/// +/// ```c,no_run +/// struct RustCallStatus { +/// int8_t code; +/// RustBuffer error_buf; +/// }; +/// ``` +#[repr(C)] +pub struct RustCallStatus { + pub code: RustCallStatusCode, + // code is signed because unsigned types are experimental in Kotlin + pub error_buf: MaybeUninit<RustBuffer>, + // error_buf is MaybeUninit to avoid dropping the value that the consumer code sends in: + // - Consumers should send in a zeroed out RustBuffer. In this case dropping is a no-op and + // avoiding the drop is a small optimization. + // - If consumers pass in invalid data, then we should avoid trying to drop it. In + // particular, we don't want to try to free any data the consumer has allocated. + // + // `MaybeUninit` requires unsafe code, since we are preventing rust from dropping the value. + // To use this safely we need to make sure that no code paths set this twice, since that will + // leak the first `RustBuffer`. +} + +impl RustCallStatus { + pub fn cancelled() -> Self { + Self { + code: RustCallStatusCode::Cancelled, + error_buf: MaybeUninit::new(RustBuffer::new()), + } + } + + pub fn error(message: impl Into<String>) -> Self { + Self { + code: RustCallStatusCode::UnexpectedError, + error_buf: MaybeUninit::new(<String as Lower<UniFfiTag>>::lower(message.into())), + } + } +} + +impl Default for RustCallStatus { + fn default() -> Self { + Self { + code: RustCallStatusCode::Success, + error_buf: MaybeUninit::uninit(), + } + } +} + +/// Result of a FFI call to a Rust function +#[repr(i8)] +#[derive(Debug, PartialEq, Eq)] +pub enum RustCallStatusCode { + /// Successful call. + Success = 0, + /// Expected error, corresponding to the `Result::Err` variant. [RustCallStatus::error_buf] + /// will contain the serialized error. + Error = 1, + /// Unexpected error. [RustCallStatus::error_buf] will contain a serialized message string + UnexpectedError = 2, + /// Async function cancelled. [RustCallStatus::error_buf] will be empty and does not need to + /// be freed. + /// + /// This is only returned for async functions and only if the bindings code uses the + /// [rust_future_cancel] call. + Cancelled = 3, +} + +/// Handle a scaffolding calls +/// +/// `callback` is responsible for making the actual Rust call and returning a special result type: +/// - For successful calls, return `Ok(value)` +/// - For errors that should be translated into thrown exceptions in the foreign code, serialize +/// the error into a `RustBuffer`, then return `Ok(buf)` +/// - The success type, must implement `FfiDefault`. +/// - `Return::lower_return` returns `Result<>` types that meet the above criteria> +/// - If the function returns a `Ok` value it will be unwrapped and returned +/// - If the function returns a `Err` value: +/// - `out_status.code` will be set to [RustCallStatusCode::Error]. +/// - `out_status.error_buf` will be set to a newly allocated `RustBuffer` containing the error. The calling +/// code is responsible for freeing the `RustBuffer` +/// - `FfiDefault::ffi_default()` is returned, although foreign code should ignore this value +/// - If the function panics: +/// - `out_status.code` will be set to `CALL_PANIC` +/// - `out_status.error_buf` will be set to a newly allocated `RustBuffer` containing a +/// serialized error message. The calling code is responsible for freeing the `RustBuffer` +/// - `FfiDefault::ffi_default()` is returned, although foreign code should ignore this value +pub fn rust_call<F, R>(out_status: &mut RustCallStatus, callback: F) -> R +where + F: panic::UnwindSafe + FnOnce() -> Result<R, RustBuffer>, + R: FfiDefault, +{ + rust_call_with_out_status(out_status, callback).unwrap_or_else(R::ffi_default) +} + +/// Make a Rust call and update `RustCallStatus` based on the result. +/// +/// If the call succeeds this returns Some(v) and doesn't touch out_status +/// If the call fails (including Err results), this returns None and updates out_status +/// +/// This contains the shared code between `rust_call` and `rustfuture::do_wake`. +pub(crate) fn rust_call_with_out_status<F, R>( + out_status: &mut RustCallStatus, + callback: F, +) -> Option<R> +where + F: panic::UnwindSafe + FnOnce() -> Result<R, RustBuffer>, +{ + let result = panic::catch_unwind(|| { + crate::panichook::ensure_setup(); + callback() + }); + match result { + // Happy path. Note: no need to update out_status in this case because the calling code + // initializes it to [RustCallStatusCode::Success] + Ok(Ok(v)) => Some(v), + // Callback returned an Err. + Ok(Err(buf)) => { + out_status.code = RustCallStatusCode::Error; + unsafe { + // Unsafe because we're setting the `MaybeUninit` value, see above for safety + // invariants. + out_status.error_buf.as_mut_ptr().write(buf); + } + None + } + // Callback panicked + Err(cause) => { + out_status.code = RustCallStatusCode::UnexpectedError; + // Try to coerce the cause into a RustBuffer containing a String. Since this code can + // panic, we need to use a second catch_unwind(). + let message_result = panic::catch_unwind(panic::AssertUnwindSafe(move || { + // The documentation suggests that it will *usually* be a str or String. + let message = if let Some(s) = cause.downcast_ref::<&'static str>() { + (*s).to_string() + } else if let Some(s) = cause.downcast_ref::<String>() { + s.clone() + } else { + "Unknown panic!".to_string() + }; + log::error!("Caught a panic calling rust code: {:?}", message); + <String as Lower<UniFfiTag>>::lower(message) + })); + if let Ok(buf) = message_result { + unsafe { + // Unsafe because we're setting the `MaybeUninit` value, see above for safety + // invariants. + out_status.error_buf.as_mut_ptr().write(buf); + } + } + // Ignore the error case. We've done all that we can at this point. In the bindings + // code, we handle this by checking if `error_buf` still has an empty `RustBuffer` and + // using a generic message. + None + } + } +} + +#[cfg(test)] +mod test { + use super::*; + use crate::{test_util::TestError, Lift, LowerReturn}; + + fn create_call_status() -> RustCallStatus { + RustCallStatus { + code: RustCallStatusCode::Success, + error_buf: MaybeUninit::new(RustBuffer::new()), + } + } + + fn test_callback(a: u8) -> Result<i8, TestError> { + match a { + 0 => Ok(100), + 1 => Err(TestError("Error".to_owned())), + x => panic!("Unexpected value: {x}"), + } + } + + #[test] + fn test_rust_call() { + let mut status = create_call_status(); + let return_value = rust_call(&mut status, || { + <Result<i8, TestError> as LowerReturn<UniFfiTag>>::lower_return(test_callback(0)) + }); + + assert_eq!(status.code, RustCallStatusCode::Success); + assert_eq!(return_value, 100); + + rust_call(&mut status, || { + <Result<i8, TestError> as LowerReturn<UniFfiTag>>::lower_return(test_callback(1)) + }); + assert_eq!(status.code, RustCallStatusCode::Error); + unsafe { + assert_eq!( + <TestError as Lift<UniFfiTag>>::try_lift(status.error_buf.assume_init()).unwrap(), + TestError("Error".to_owned()) + ); + } + + let mut status = create_call_status(); + rust_call(&mut status, || { + <Result<i8, TestError> as LowerReturn<UniFfiTag>>::lower_return(test_callback(2)) + }); + assert_eq!(status.code, RustCallStatusCode::UnexpectedError); + unsafe { + assert_eq!( + <String as Lift<UniFfiTag>>::try_lift(status.error_buf.assume_init()).unwrap(), + "Unexpected value: 2" + ); + } + } +} diff --git a/src/ffi/rustfuture/future.rs b/src/ffi/rustfuture/future.rs new file mode 100644 index 0000000..b104b20 --- /dev/null +++ b/src/ffi/rustfuture/future.rs @@ -0,0 +1,320 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +//! [`RustFuture`] represents a [`Future`] that can be sent to the foreign code over FFI. +//! +//! This type is not instantiated directly, but via the procedural macros, such as `#[uniffi::export]`. +//! +//! # The big picture +//! +//! We implement async foreign functions using a simplified version of the Future API: +//! +//! 0. At startup, register a [RustFutureContinuationCallback] by calling +//! rust_future_continuation_callback_set. +//! 1. Call the scaffolding function to get a [RustFutureHandle] +//! 2a. In a loop: +//! - Call [rust_future_poll] +//! - Suspend the function until the [rust_future_poll] continuation function is called +//! - If the continuation was function was called with [RustFuturePoll::Ready], then break +//! otherwise continue. +//! 2b. If the async function is cancelled, then call [rust_future_cancel]. This causes the +//! continuation function to be called with [RustFuturePoll::Ready] and the [RustFuture] to +//! enter a cancelled state. +//! 3. Call [rust_future_complete] to get the result of the future. +//! 4. Call [rust_future_free] to free the future, ideally in a finally block. This: +//! - Releases any resources held by the future +//! - Calls any continuation callbacks that have not been called yet +//! +//! Note: Technically, the foreign code calls the scaffolding versions of the `rust_future_*` +//! functions. These are generated by the scaffolding macro, specially prefixed, and extern "C", +//! and manually monomorphized in the case of [rust_future_complete]. See +//! `uniffi_macros/src/setup_scaffolding.rs` for details. +//! +//! ## How does `Future` work exactly? +//! +//! A [`Future`] in Rust does nothing. When calling an async function, it just +//! returns a `Future` but nothing has happened yet. To start the computation, +//! the future must be polled. It returns [`Poll::Ready(r)`][`Poll::Ready`] if +//! the result is ready, [`Poll::Pending`] otherwise. `Poll::Pending` basically +//! means: +//! +//! > Please, try to poll me later, maybe the result will be ready! +//! +//! This model is very different than what other languages do, but it can actually +//! be translated quite easily, fortunately for us! +//! +//! But… wait a minute… who is responsible to poll the `Future` if a `Future` does +//! nothing? Well, it's _the executor_. The executor is responsible _to drive_ the +//! `Future`: that's where they are polled. +//! +//! But… wait another minute… how does the executor know when to poll a [`Future`]? +//! Does it poll them randomly in an endless loop? Well, no, actually it depends +//! on the executor! A well-designed `Future` and executor work as follows. +//! Normally, when [`Future::poll`] is called, a [`Context`] argument is +//! passed to it. It contains a [`Waker`]. The [`Waker`] is built on top of a +//! [`RawWaker`] which implements whatever is necessary. Usually, a waker will +//! signal the executor to poll a particular `Future`. A `Future` will clone +//! or pass-by-ref the waker to somewhere, as a callback, a completion, a +//! function, or anything, to the system that is responsible to notify when a +//! task is completed. So, to recap, the waker is _not_ responsible for waking the +//! `Future`, it _is_ responsible for _signaling_ the executor that a particular +//! `Future` should be polled again. That's why the documentation of +//! [`Poll::Pending`] specifies: +//! +//! > When a function returns `Pending`, the function must also ensure that the +//! > current task is scheduled to be awoken when progress can be made. +//! +//! “awakening” is done by using the `Waker`. +//! +//! [`Future`]: https://doc.rust-lang.org/std/future/trait.Future.html +//! [`Future::poll`]: https://doc.rust-lang.org/std/future/trait.Future.html#tymethod.poll +//! [`Pol::Ready`]: https://doc.rust-lang.org/std/task/enum.Poll.html#variant.Ready +//! [`Poll::Pending`]: https://doc.rust-lang.org/std/task/enum.Poll.html#variant.Pending +//! [`Context`]: https://doc.rust-lang.org/std/task/struct.Context.html +//! [`Waker`]: https://doc.rust-lang.org/std/task/struct.Waker.html +//! [`RawWaker`]: https://doc.rust-lang.org/std/task/struct.RawWaker.html + +use std::{ + future::Future, + marker::PhantomData, + ops::Deref, + panic, + pin::Pin, + sync::{Arc, Mutex}, + task::{Context, Poll, Wake}, +}; + +use super::{RustFutureContinuationCallback, RustFuturePoll, Scheduler}; +use crate::{rust_call_with_out_status, FfiDefault, LowerReturn, RustCallStatus}; + +/// Wraps the actual future we're polling +struct WrappedFuture<F, T, UT> +where + // See rust_future_new for an explanation of these trait bounds + F: Future<Output = T> + Send + 'static, + T: LowerReturn<UT> + Send + 'static, + UT: Send + 'static, +{ + // Note: this could be a single enum, but that would make it easy to mess up the future pinning + // guarantee. For example you might want to call `std::mem::take()` to try to get the result, + // but if the future happened to be stored that would move and break all internal references. + future: Option<F>, + result: Option<Result<T::ReturnType, RustCallStatus>>, +} + +impl<F, T, UT> WrappedFuture<F, T, UT> +where + // See rust_future_new for an explanation of these trait bounds + F: Future<Output = T> + Send + 'static, + T: LowerReturn<UT> + Send + 'static, + UT: Send + 'static, +{ + fn new(future: F) -> Self { + Self { + future: Some(future), + result: None, + } + } + + // Poll the future and check if it's ready or not + fn poll(&mut self, context: &mut Context<'_>) -> bool { + if self.result.is_some() { + true + } else if let Some(future) = &mut self.future { + // SAFETY: We can call Pin::new_unchecked because: + // - This is the only time we get a &mut to `self.future` + // - We never poll the future after it's moved (for example by using take()) + // - We never move RustFuture, which contains us. + // - RustFuture is private to this module so no other code can move it. + let pinned = unsafe { Pin::new_unchecked(future) }; + // Run the poll and lift the result if it's ready + let mut out_status = RustCallStatus::default(); + let result: Option<Poll<T::ReturnType>> = rust_call_with_out_status( + &mut out_status, + // This closure uses a `&mut F` value, which means it's not UnwindSafe by + // default. If the future panics, it may be in an invalid state. + // + // However, we can safely use `AssertUnwindSafe` since a panic will lead the `None` + // case below and we will never poll the future again. + panic::AssertUnwindSafe(|| match pinned.poll(context) { + Poll::Pending => Ok(Poll::Pending), + Poll::Ready(v) => T::lower_return(v).map(Poll::Ready), + }), + ); + match result { + Some(Poll::Pending) => false, + Some(Poll::Ready(v)) => { + self.future = None; + self.result = Some(Ok(v)); + true + } + None => { + self.future = None; + self.result = Some(Err(out_status)); + true + } + } + } else { + log::error!("poll with neither future nor result set"); + true + } + } + + fn complete(&mut self, out_status: &mut RustCallStatus) -> T::ReturnType { + let mut return_value = T::ReturnType::ffi_default(); + match self.result.take() { + Some(Ok(v)) => return_value = v, + Some(Err(call_status)) => *out_status = call_status, + None => *out_status = RustCallStatus::cancelled(), + } + self.free(); + return_value + } + + fn free(&mut self) { + self.future = None; + self.result = None; + } +} + +// If F and T are Send, then WrappedFuture is too +// +// Rust will not mark it Send by default when T::ReturnType is a raw pointer. This is promising +// that we will treat the raw pointer properly, for example by not returning it twice. +unsafe impl<F, T, UT> Send for WrappedFuture<F, T, UT> +where + // See rust_future_new for an explanation of these trait bounds + F: Future<Output = T> + Send + 'static, + T: LowerReturn<UT> + Send + 'static, + UT: Send + 'static, +{ +} + +/// Future that the foreign code is awaiting +pub(super) struct RustFuture<F, T, UT> +where + // See rust_future_new for an explanation of these trait bounds + F: Future<Output = T> + Send + 'static, + T: LowerReturn<UT> + Send + 'static, + UT: Send + 'static, +{ + // This Mutex should never block if our code is working correctly, since there should not be + // multiple threads calling [Self::poll] and/or [Self::complete] at the same time. + future: Mutex<WrappedFuture<F, T, UT>>, + scheduler: Mutex<Scheduler>, + // UT is used as the generic parameter for [LowerReturn]. + // Let's model this with PhantomData as a function that inputs a UT value. + _phantom: PhantomData<fn(UT) -> ()>, +} + +impl<F, T, UT> RustFuture<F, T, UT> +where + // See rust_future_new for an explanation of these trait bounds + F: Future<Output = T> + Send + 'static, + T: LowerReturn<UT> + Send + 'static, + UT: Send + 'static, +{ + pub(super) fn new(future: F, _tag: UT) -> Arc<Self> { + Arc::new(Self { + future: Mutex::new(WrappedFuture::new(future)), + scheduler: Mutex::new(Scheduler::new()), + _phantom: PhantomData, + }) + } + + pub(super) fn poll(self: Arc<Self>, callback: RustFutureContinuationCallback, data: *const ()) { + let ready = self.is_cancelled() || { + let mut locked = self.future.lock().unwrap(); + let waker: std::task::Waker = Arc::clone(&self).into(); + locked.poll(&mut Context::from_waker(&waker)) + }; + if ready { + callback(data, RustFuturePoll::Ready) + } else { + self.scheduler.lock().unwrap().store(callback, data); + } + } + + pub(super) fn is_cancelled(&self) -> bool { + self.scheduler.lock().unwrap().is_cancelled() + } + + pub(super) fn wake(&self) { + self.scheduler.lock().unwrap().wake(); + } + + pub(super) fn cancel(&self) { + self.scheduler.lock().unwrap().cancel(); + } + + pub(super) fn complete(&self, call_status: &mut RustCallStatus) -> T::ReturnType { + self.future.lock().unwrap().complete(call_status) + } + + pub(super) fn free(self: Arc<Self>) { + // Call cancel() to send any leftover data to the continuation callback + self.scheduler.lock().unwrap().cancel(); + // Ensure we drop our inner future, releasing all held references + self.future.lock().unwrap().free(); + } +} + +impl<F, T, UT> Wake for RustFuture<F, T, UT> +where + // See rust_future_new for an explanation of these trait bounds + F: Future<Output = T> + Send + 'static, + T: LowerReturn<UT> + Send + 'static, + UT: Send + 'static, +{ + fn wake(self: Arc<Self>) { + self.deref().wake() + } + + fn wake_by_ref(self: &Arc<Self>) { + self.deref().wake() + } +} + +/// RustFuture FFI trait. This allows `Arc<RustFuture<F, T, UT>>` to be cast to +/// `Arc<dyn RustFutureFfi<T::ReturnType>>`, which is needed to implement the public FFI API. In particular, this +/// allows you to use RustFuture functionality without knowing the concrete Future type, which is +/// unnamable. +/// +/// This is parametrized on the ReturnType rather than the `T` directly, to reduce the number of +/// scaffolding functions we need to generate. If it was parametrized on `T`, then we would need +/// to create a poll, cancel, complete, and free scaffolding function for each exported async +/// function. That would add ~1kb binary size per exported function based on a quick estimate on a +/// x86-64 machine . By parametrizing on `T::ReturnType` we can instead monomorphize by hand and +/// only create those functions for each of the 13 possible FFI return types. +#[doc(hidden)] +pub trait RustFutureFfi<ReturnType> { + fn ffi_poll(self: Arc<Self>, callback: RustFutureContinuationCallback, data: *const ()); + fn ffi_cancel(&self); + fn ffi_complete(&self, call_status: &mut RustCallStatus) -> ReturnType; + fn ffi_free(self: Arc<Self>); +} + +impl<F, T, UT> RustFutureFfi<T::ReturnType> for RustFuture<F, T, UT> +where + // See rust_future_new for an explanation of these trait bounds + F: Future<Output = T> + Send + 'static, + T: LowerReturn<UT> + Send + 'static, + UT: Send + 'static, +{ + fn ffi_poll(self: Arc<Self>, callback: RustFutureContinuationCallback, data: *const ()) { + self.poll(callback, data) + } + + fn ffi_cancel(&self) { + self.cancel() + } + + fn ffi_complete(&self, call_status: &mut RustCallStatus) -> T::ReturnType { + self.complete(call_status) + } + + fn ffi_free(self: Arc<Self>) { + self.free(); + } +} diff --git a/src/ffi/rustfuture/mod.rs b/src/ffi/rustfuture/mod.rs new file mode 100644 index 0000000..4aaf013 --- /dev/null +++ b/src/ffi/rustfuture/mod.rs @@ -0,0 +1,126 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +use std::{future::Future, sync::Arc}; + +mod future; +mod scheduler; +use future::*; +use scheduler::*; + +#[cfg(test)] +mod tests; + +use crate::{LowerReturn, RustCallStatus}; + +/// Result code for [rust_future_poll]. This is passed to the continuation function. +#[repr(i8)] +#[derive(Debug, PartialEq, Eq)] +pub enum RustFuturePoll { + /// The future is ready and is waiting for [rust_future_complete] to be called + Ready = 0, + /// The future might be ready and [rust_future_poll] should be called again + MaybeReady = 1, +} + +/// Foreign callback that's passed to [rust_future_poll] +/// +/// The Rust side of things calls this when the foreign side should call [rust_future_poll] again +/// to continue progress on the future. +pub type RustFutureContinuationCallback = extern "C" fn(callback_data: *const (), RustFuturePoll); + +/// Opaque handle for a Rust future that's stored by the foreign language code +#[repr(transparent)] +pub struct RustFutureHandle(*const ()); + +// === Public FFI API === + +/// Create a new [RustFutureHandle] +/// +/// For each exported async function, UniFFI will create a scaffolding function that uses this to +/// create the [RustFutureHandle] to pass to the foreign code. +pub fn rust_future_new<F, T, UT>(future: F, tag: UT) -> RustFutureHandle +where + // F is the future type returned by the exported async function. It needs to be Send + `static + // since it will move between threads for an indeterminate amount of time as the foreign + // executor calls polls it and the Rust executor wakes it. It does not need to by `Sync`, + // since we synchronize all access to the values. + F: Future<Output = T> + Send + 'static, + // T is the output of the Future. It needs to implement [LowerReturn]. Also it must be Send + + // 'static for the same reason as F. + T: LowerReturn<UT> + Send + 'static, + // The UniFfiTag ZST. The Send + 'static bound is to keep rustc happy. + UT: Send + 'static, +{ + // Create a RustFuture and coerce to `Arc<dyn RustFutureFfi>`, which is what we use to + // implement the FFI + let future_ffi = RustFuture::new(future, tag) as Arc<dyn RustFutureFfi<T::ReturnType>>; + // Box the Arc, to convert the wide pointer into a normal sized pointer so that we can pass it + // to the foreign code. + let boxed_ffi = Box::new(future_ffi); + // We can now create a RustFutureHandle + RustFutureHandle(Box::into_raw(boxed_ffi) as *mut ()) +} + +/// Poll a Rust future +/// +/// When the future is ready to progress the continuation will be called with the `data` value and +/// a [RustFuturePoll] value. For each [rust_future_poll] call the continuation will be called +/// exactly once. +/// +/// # Safety +/// +/// The [RustFutureHandle] must not previously have been passed to [rust_future_free] +pub unsafe fn rust_future_poll<ReturnType>( + handle: RustFutureHandle, + callback: RustFutureContinuationCallback, + data: *const (), +) { + let future = &*(handle.0 as *mut Arc<dyn RustFutureFfi<ReturnType>>); + future.clone().ffi_poll(callback, data) +} + +/// Cancel a Rust future +/// +/// Any current and future continuations will be immediately called with RustFuturePoll::Ready. +/// +/// This is needed for languages like Swift, which continuation to wait for the continuation to be +/// called when tasks are cancelled. +/// +/// # Safety +/// +/// The [RustFutureHandle] must not previously have been passed to [rust_future_free] +pub unsafe fn rust_future_cancel<ReturnType>(handle: RustFutureHandle) { + let future = &*(handle.0 as *mut Arc<dyn RustFutureFfi<ReturnType>>); + future.clone().ffi_cancel() +} + +/// Complete a Rust future +/// +/// Note: the actually extern "C" scaffolding functions can't be generic, so we generate one for +/// each supported FFI type. +/// +/// # Safety +/// +/// - The [RustFutureHandle] must not previously have been passed to [rust_future_free] +/// - The `T` param must correctly correspond to the [rust_future_new] call. It must +/// be `<Output as LowerReturn<UT>>::ReturnType` +pub unsafe fn rust_future_complete<ReturnType>( + handle: RustFutureHandle, + out_status: &mut RustCallStatus, +) -> ReturnType { + let future = &*(handle.0 as *mut Arc<dyn RustFutureFfi<ReturnType>>); + future.ffi_complete(out_status) +} + +/// Free a Rust future, dropping the strong reference and releasing all references held by the +/// future. +/// +/// # Safety +/// +/// The [RustFutureHandle] must not previously have been passed to [rust_future_free] +pub unsafe fn rust_future_free<ReturnType>(handle: RustFutureHandle) { + let future = Box::from_raw(handle.0 as *mut Arc<dyn RustFutureFfi<ReturnType>>); + future.ffi_free() +} diff --git a/src/ffi/rustfuture/scheduler.rs b/src/ffi/rustfuture/scheduler.rs new file mode 100644 index 0000000..aae5a0c --- /dev/null +++ b/src/ffi/rustfuture/scheduler.rs @@ -0,0 +1,96 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +use std::mem; + +use super::{RustFutureContinuationCallback, RustFuturePoll}; + +/// Schedules a [crate::RustFuture] by managing the continuation data +/// +/// This struct manages the continuation callback and data that comes from the foreign side. It +/// is responsible for calling the continuation callback when the future is ready to be woken up. +/// +/// The basic guarantees are: +/// +/// * Each callback will be invoked exactly once, with its associated data. +/// * If `wake()` is called, the callback will be invoked to wake up the future -- either +/// immediately or the next time we get a callback. +/// * If `cancel()` is called, the same will happen and the schedule will stay in the cancelled +/// state, invoking any future callbacks as soon as they're stored. + +#[derive(Debug)] +pub(super) enum Scheduler { + /// No continuations set, neither wake() nor cancel() called. + Empty, + /// `wake()` was called when there was no continuation set. The next time `store` is called, + /// the continuation should be immediately invoked with `RustFuturePoll::MaybeReady` + Waked, + /// The future has been cancelled, any future `store` calls should immediately result in the + /// continuation being called with `RustFuturePoll::Ready`. + Cancelled, + /// Continuation set, the next time `wake()` is called is called, we should invoke it. + Set(RustFutureContinuationCallback, *const ()), +} + +impl Scheduler { + pub(super) fn new() -> Self { + Self::Empty + } + + /// Store new continuation data if we are in the `Empty` state. If we are in the `Waked` or + /// `Cancelled` state, call the continuation immediately with the data. + pub(super) fn store(&mut self, callback: RustFutureContinuationCallback, data: *const ()) { + match self { + Self::Empty => *self = Self::Set(callback, data), + Self::Set(old_callback, old_data) => { + log::error!( + "store: observed `Self::Set` state. Is poll() being called from multiple threads at once?" + ); + old_callback(*old_data, RustFuturePoll::Ready); + *self = Self::Set(callback, data); + } + Self::Waked => { + *self = Self::Empty; + callback(data, RustFuturePoll::MaybeReady); + } + Self::Cancelled => { + callback(data, RustFuturePoll::Ready); + } + } + } + + pub(super) fn wake(&mut self) { + match self { + // If we had a continuation set, then call it and transition to the `Empty` state. + Self::Set(callback, old_data) => { + let old_data = *old_data; + let callback = *callback; + *self = Self::Empty; + callback(old_data, RustFuturePoll::MaybeReady); + } + // If we were in the `Empty` state, then transition to `Waked`. The next time `store` + // is called, we will immediately call the continuation. + Self::Empty => *self = Self::Waked, + // This is a no-op if we were in the `Cancelled` or `Waked` state. + _ => (), + } + } + + pub(super) fn cancel(&mut self) { + if let Self::Set(callback, old_data) = mem::replace(self, Self::Cancelled) { + callback(old_data, RustFuturePoll::Ready); + } + } + + pub(super) fn is_cancelled(&self) -> bool { + matches!(self, Self::Cancelled) + } +} + +// The `*const ()` data pointer references an object on the foreign side. +// This object must be `Sync` in Rust terminology -- it must be safe for us to pass the pointer to the continuation callback from any thread. +// If the foreign side upholds their side of the contract, then `Scheduler` is Send + Sync. + +unsafe impl Send for Scheduler {} +unsafe impl Sync for Scheduler {} diff --git a/src/ffi/rustfuture/tests.rs b/src/ffi/rustfuture/tests.rs new file mode 100644 index 0000000..1f68085 --- /dev/null +++ b/src/ffi/rustfuture/tests.rs @@ -0,0 +1,223 @@ +use once_cell::sync::OnceCell; +use std::{ + future::Future, + panic, + pin::Pin, + sync::{Arc, Mutex}, + task::{Context, Poll, Waker}, +}; + +use super::*; +use crate::{test_util::TestError, Lift, RustBuffer, RustCallStatusCode}; + +// Sender/Receiver pair that we use for testing +struct Channel { + result: Option<Result<String, TestError>>, + waker: Option<Waker>, +} + +struct Sender(Arc<Mutex<Channel>>); + +impl Sender { + fn wake(&self) { + let inner = self.0.lock().unwrap(); + if let Some(waker) = &inner.waker { + waker.wake_by_ref(); + } + } + + fn send(&self, value: Result<String, TestError>) { + let mut inner = self.0.lock().unwrap(); + if inner.result.replace(value).is_some() { + panic!("value already sent"); + } + if let Some(waker) = &inner.waker { + waker.wake_by_ref(); + } + } +} + +struct Receiver(Arc<Mutex<Channel>>); + +impl Future for Receiver { + type Output = Result<String, TestError>; + + fn poll(self: Pin<&mut Self>, context: &mut Context<'_>) -> Poll<Result<String, TestError>> { + let mut inner = self.0.lock().unwrap(); + match &inner.result { + Some(v) => Poll::Ready(v.clone()), + None => { + inner.waker = Some(context.waker().clone()); + Poll::Pending + } + } + } +} + +// Create a sender and rust future that we can use for testing +fn channel() -> (Sender, Arc<dyn RustFutureFfi<RustBuffer>>) { + let channel = Arc::new(Mutex::new(Channel { + result: None, + waker: None, + })); + let rust_future = RustFuture::new(Receiver(channel.clone()), crate::UniFfiTag); + (Sender(channel), rust_future) +} + +/// Poll a Rust future and get an OnceCell that's set when the continuation is called +fn poll(rust_future: &Arc<dyn RustFutureFfi<RustBuffer>>) -> Arc<OnceCell<RustFuturePoll>> { + let cell = Arc::new(OnceCell::new()); + let cell_ptr = Arc::into_raw(cell.clone()) as *const (); + rust_future.clone().ffi_poll(poll_continuation, cell_ptr); + cell +} + +extern "C" fn poll_continuation(data: *const (), code: RustFuturePoll) { + let cell = unsafe { Arc::from_raw(data as *const OnceCell<RustFuturePoll>) }; + cell.set(code).expect("Error setting OnceCell"); +} + +fn complete(rust_future: Arc<dyn RustFutureFfi<RustBuffer>>) -> (RustBuffer, RustCallStatus) { + let mut out_status_code = RustCallStatus::default(); + let return_value = rust_future.ffi_complete(&mut out_status_code); + (return_value, out_status_code) +} + +#[test] +fn test_success() { + let (sender, rust_future) = channel(); + + // Test polling the rust future before it's ready + let continuation_result = poll(&rust_future); + assert_eq!(continuation_result.get(), None); + sender.wake(); + assert_eq!(continuation_result.get(), Some(&RustFuturePoll::MaybeReady)); + + // Test polling the rust future when it's ready + let continuation_result = poll(&rust_future); + assert_eq!(continuation_result.get(), None); + sender.send(Ok("All done".into())); + assert_eq!(continuation_result.get(), Some(&RustFuturePoll::MaybeReady)); + + // Future polls should immediately return ready + let continuation_result = poll(&rust_future); + assert_eq!(continuation_result.get(), Some(&RustFuturePoll::Ready)); + + // Complete the future + let (return_buf, call_status) = complete(rust_future); + assert_eq!(call_status.code, RustCallStatusCode::Success); + assert_eq!( + <String as Lift<crate::UniFfiTag>>::try_lift(return_buf).unwrap(), + "All done" + ); +} + +#[test] +fn test_error() { + let (sender, rust_future) = channel(); + + let continuation_result = poll(&rust_future); + assert_eq!(continuation_result.get(), None); + sender.send(Err("Something went wrong".into())); + assert_eq!(continuation_result.get(), Some(&RustFuturePoll::MaybeReady)); + + let continuation_result = poll(&rust_future); + assert_eq!(continuation_result.get(), Some(&RustFuturePoll::Ready)); + + let (_, call_status) = complete(rust_future); + assert_eq!(call_status.code, RustCallStatusCode::Error); + unsafe { + assert_eq!( + <TestError as Lift<crate::UniFfiTag>>::try_lift_from_rust_buffer( + call_status.error_buf.assume_init() + ) + .unwrap(), + TestError::from("Something went wrong"), + ) + } +} + +// Once `complete` is called, the inner future should be released, even if wakers still hold a +// reference to the RustFuture +#[test] +fn test_cancel() { + let (_sender, rust_future) = channel(); + + let continuation_result = poll(&rust_future); + assert_eq!(continuation_result.get(), None); + rust_future.ffi_cancel(); + // Cancellation should immediately invoke the callback with RustFuturePoll::Ready + assert_eq!(continuation_result.get(), Some(&RustFuturePoll::Ready)); + + // Future polls should immediately invoke the callback with RustFuturePoll::Ready + let continuation_result = poll(&rust_future); + assert_eq!(continuation_result.get(), Some(&RustFuturePoll::Ready)); + + let (_, call_status) = complete(rust_future); + assert_eq!(call_status.code, RustCallStatusCode::Cancelled); +} + +// Once `free` is called, the inner future should be released, even if wakers still hold a +// reference to the RustFuture +#[test] +fn test_release_future() { + let (sender, rust_future) = channel(); + // Create a weak reference to the channel to use to check if rust_future has dropped its + // future. + let channel_weak = Arc::downgrade(&sender.0); + drop(sender); + // Create an extra ref to rust_future, simulating a waker that still holds a reference to + // it + let rust_future2 = rust_future.clone(); + + // Complete the rust future + rust_future.ffi_free(); + // Even though rust_future is still alive, the channel shouldn't be + assert!(Arc::strong_count(&rust_future2) > 0); + assert_eq!(channel_weak.strong_count(), 0); + assert!(channel_weak.upgrade().is_none()); +} + +// If `free` is called with a continuation still stored, we should call it them then. +// +// This shouldn't happen in practice, but it seems like good defensive programming +#[test] +fn test_complete_with_stored_continuation() { + let (_sender, rust_future) = channel(); + + let continuation_result = poll(&rust_future); + rust_future.ffi_free(); + assert_eq!(continuation_result.get(), Some(&RustFuturePoll::Ready)); +} + +// Test what happens if we see a `wake()` call while we're polling the future. This can +// happen, for example, with futures that are handled by a tokio thread pool. We should +// schedule another poll of the future in this case. +#[test] +fn test_wake_during_poll() { + let mut first_time = true; + let future = std::future::poll_fn(move |ctx| { + if first_time { + first_time = false; + // Wake the future while we are in the middle of polling it + ctx.waker().clone().wake(); + Poll::Pending + } else { + // The second time we're polled, we're ready + Poll::Ready("All done".to_owned()) + } + }); + let rust_future: Arc<dyn RustFutureFfi<RustBuffer>> = RustFuture::new(future, crate::UniFfiTag); + let continuation_result = poll(&rust_future); + // The continuation function should called immediately + assert_eq!(continuation_result.get(), Some(&RustFuturePoll::MaybeReady)); + // A second poll should finish the future + let continuation_result = poll(&rust_future); + assert_eq!(continuation_result.get(), Some(&RustFuturePoll::Ready)); + let (return_buf, call_status) = complete(rust_future); + assert_eq!(call_status.code, RustCallStatusCode::Success); + assert_eq!( + <String as Lift<crate::UniFfiTag>>::try_lift(return_buf).unwrap(), + "All done" + ); +} diff --git a/src/ffi_converter_impls.rs b/src/ffi_converter_impls.rs new file mode 100644 index 0000000..5be5f04 --- /dev/null +++ b/src/ffi_converter_impls.rs @@ -0,0 +1,531 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/// This module contains builtin `FFIConverter` implementations. These cover: +/// - Simple privitive types: u8, i32, String, Arc<T>, etc +/// - Composite types: Vec<T>, Option<T>, etc. +/// - SystemTime and Duration, which maybe shouldn`t be built-in, but have been historically and +/// we want to continue to support them for now. +/// +/// As described in +/// https://mozilla.github.io/uniffi-rs/internals/lifting_and_lowering.html#code-generation-and-the-fficonverter-trait, +/// we use the following system: +/// +/// - Each UniFFIed crate defines a unit struct named `UniFfiTag` +/// - We define an `impl FFIConverter<UniFfiTag> for Type` for each type that we want to pass +/// across the FFI. +/// - When generating the code, we use the `<T as ::uniffi::FFIConverter<crate::UniFfiTag>>` impl +/// to lift/lower/serialize types for a crate. +/// +/// This crate needs to implement `FFIConverter<UT>` on `UniFfiTag` instances for all UniFFI +/// consumer crates. To do this, it defines blanket impls like `impl<UT> FFIConverter<UT> for u8`. +/// "UT" means an arbitrary `UniFfiTag` type. +use crate::{ + check_remaining, derive_ffi_traits, ffi_converter_rust_buffer_lift_and_lower, metadata, + ConvertError, FfiConverter, Lift, LiftRef, LiftReturn, Lower, LowerReturn, MetadataBuffer, + Result, RustBuffer, UnexpectedUniFFICallbackError, +}; +use anyhow::bail; +use bytes::buf::{Buf, BufMut}; +use paste::paste; +use std::{ + collections::HashMap, + convert::TryFrom, + error::Error, + sync::Arc, + time::{Duration, SystemTime}, +}; + +/// Blanket implementation of `FfiConverter` for numeric primitives. +/// +/// Numeric primitives have a straightforward mapping into C-compatible numeric types, +/// sice they are themselves a C-compatible numeric type! +macro_rules! impl_ffi_converter_for_num_primitive { + ($T:ty, $type_code:expr) => { + paste! { + unsafe impl<UT> FfiConverter<UT> for $T { + type FfiType = $T; + + fn lower(obj: $T) -> Self::FfiType { + obj + } + + fn try_lift(v: Self::FfiType) -> Result<$T> { + Ok(v) + } + + fn write(obj: $T, buf: &mut Vec<u8>) { + buf.[<put_ $T>](obj); + } + + fn try_read(buf: &mut &[u8]) -> Result<$T> { + check_remaining(buf, std::mem::size_of::<$T>())?; + Ok(buf.[<get_ $T>]()) + } + + const TYPE_ID_META: MetadataBuffer = MetadataBuffer::from_code($type_code); + } + } + }; +} + +impl_ffi_converter_for_num_primitive!(u8, metadata::codes::TYPE_U8); +impl_ffi_converter_for_num_primitive!(i8, metadata::codes::TYPE_I8); +impl_ffi_converter_for_num_primitive!(u16, metadata::codes::TYPE_U16); +impl_ffi_converter_for_num_primitive!(i16, metadata::codes::TYPE_I16); +impl_ffi_converter_for_num_primitive!(u32, metadata::codes::TYPE_U32); +impl_ffi_converter_for_num_primitive!(i32, metadata::codes::TYPE_I32); +impl_ffi_converter_for_num_primitive!(u64, metadata::codes::TYPE_U64); +impl_ffi_converter_for_num_primitive!(i64, metadata::codes::TYPE_I64); +impl_ffi_converter_for_num_primitive!(f32, metadata::codes::TYPE_F32); +impl_ffi_converter_for_num_primitive!(f64, metadata::codes::TYPE_F64); + +/// Support for passing boolean values via the FFI. +/// +/// Booleans are passed as an `i8` in order to avoid problems with handling +/// C-compatible boolean values on JVM-based languages. +unsafe impl<UT> FfiConverter<UT> for bool { + type FfiType = i8; + + fn lower(obj: bool) -> Self::FfiType { + i8::from(obj) + } + + fn try_lift(v: Self::FfiType) -> Result<bool> { + Ok(match v { + 0 => false, + 1 => true, + _ => bail!("unexpected byte for Boolean"), + }) + } + + fn write(obj: bool, buf: &mut Vec<u8>) { + buf.put_i8(<Self as FfiConverter<UT>>::lower(obj)); + } + + fn try_read(buf: &mut &[u8]) -> Result<bool> { + check_remaining(buf, 1)?; + <Self as FfiConverter<UT>>::try_lift(buf.get_i8()) + } + + const TYPE_ID_META: MetadataBuffer = MetadataBuffer::from_code(metadata::codes::TYPE_BOOL); +} + +/// Support for passing Strings via the FFI. +/// +/// Unlike many other implementations of `FfiConverter`, this passes a struct containing +/// a raw pointer rather than copying the data from one side to the other. This is a +/// safety hazard, but turns out to be pretty nice for useability. This struct +/// *must* be a valid `RustBuffer` and it *must* contain valid utf-8 data (in other +/// words, it *must* be a `Vec<u8>` suitable for use as an actual rust `String`). +/// +/// When serialized in a buffer, strings are represented as a i32 byte length +/// followed by utf8-encoded bytes. (It's a signed integer because unsigned types are +/// currently experimental in Kotlin). +unsafe impl<UT> FfiConverter<UT> for String { + type FfiType = RustBuffer; + + // This returns a struct with a raw pointer to the underlying bytes, so it's very + // important that it consume ownership of the String, which is relinquished to the + // foreign language code (and can be restored by it passing the pointer back). + fn lower(obj: String) -> Self::FfiType { + RustBuffer::from_vec(obj.into_bytes()) + } + + // The argument here *must* be a uniquely-owned `RustBuffer` previously obtained + // from `lower` above, and hence must be the bytes of a valid rust string. + fn try_lift(v: Self::FfiType) -> Result<String> { + let v = v.destroy_into_vec(); + // This turns the buffer back into a `String` without copying the data + // and without re-checking it for validity of the utf8. If the `RustBuffer` + // came from a valid String then there's no point in re-checking the utf8, + // and if it didn't then bad things are probably going to happen regardless + // of whether we check for valid utf8 data or not. + Ok(unsafe { String::from_utf8_unchecked(v) }) + } + + fn write(obj: String, buf: &mut Vec<u8>) { + // N.B. `len()` gives us the length in bytes, not in chars or graphemes. + // TODO: it would be nice not to panic here. + let len = i32::try_from(obj.len()).unwrap(); + buf.put_i32(len); // We limit strings to u32::MAX bytes + buf.put(obj.as_bytes()); + } + + fn try_read(buf: &mut &[u8]) -> Result<String> { + check_remaining(buf, 4)?; + let len = usize::try_from(buf.get_i32())?; + check_remaining(buf, len)?; + // N.B: In the general case `Buf::chunk()` may return partial data. + // But in the specific case of `<&[u8] as Buf>` it returns the full slice, + // so there is no risk of having less than `len` bytes available here. + let bytes = &buf.chunk()[..len]; + let res = String::from_utf8(bytes.to_vec())?; + buf.advance(len); + Ok(res) + } + + const TYPE_ID_META: MetadataBuffer = MetadataBuffer::from_code(metadata::codes::TYPE_STRING); +} + +/// Support for passing timestamp values via the FFI. +/// +/// Timestamps values are currently always passed by serializing to a buffer. +/// +/// Timestamps are represented on the buffer by an i64 that indicates the +/// direction and the magnitude in seconds of the offset from epoch, and a +/// u32 that indicates the nanosecond portion of the offset magnitude. The +/// nanosecond portion is expected to be between 0 and 999,999,999. +/// +/// To build an epoch offset the absolute value of the seconds portion of the +/// offset should be combined with the nanosecond portion. This is because +/// the sign of the seconds portion represents the direction of the offset +/// overall. The sign of the seconds portion can then be used to determine +/// if the total offset should be added to or subtracted from the unix epoch. +unsafe impl<UT> FfiConverter<UT> for SystemTime { + ffi_converter_rust_buffer_lift_and_lower!(UT); + + fn write(obj: SystemTime, buf: &mut Vec<u8>) { + let mut sign = 1; + let epoch_offset = obj + .duration_since(SystemTime::UNIX_EPOCH) + .unwrap_or_else(|error| { + sign = -1; + error.duration() + }); + // This panic should never happen as SystemTime typically stores seconds as i64 + let seconds = sign + * i64::try_from(epoch_offset.as_secs()) + .expect("SystemTime overflow, seconds greater than i64::MAX"); + + buf.put_i64(seconds); + buf.put_u32(epoch_offset.subsec_nanos()); + } + + fn try_read(buf: &mut &[u8]) -> Result<SystemTime> { + check_remaining(buf, 12)?; + let seconds = buf.get_i64(); + let nanos = buf.get_u32(); + let epoch_offset = Duration::new(seconds.wrapping_abs() as u64, nanos); + + if seconds >= 0 { + Ok(SystemTime::UNIX_EPOCH + epoch_offset) + } else { + Ok(SystemTime::UNIX_EPOCH - epoch_offset) + } + } + + const TYPE_ID_META: MetadataBuffer = + MetadataBuffer::from_code(metadata::codes::TYPE_SYSTEM_TIME); +} + +/// Support for passing duration values via the FFI. +/// +/// Duration values are currently always passed by serializing to a buffer. +/// +/// Durations are represented on the buffer by a u64 that indicates the +/// magnitude in seconds, and a u32 that indicates the nanosecond portion +/// of the magnitude. The nanosecond portion is expected to be between 0 +/// and 999,999,999. +unsafe impl<UT> FfiConverter<UT> for Duration { + ffi_converter_rust_buffer_lift_and_lower!(UT); + + fn write(obj: Duration, buf: &mut Vec<u8>) { + buf.put_u64(obj.as_secs()); + buf.put_u32(obj.subsec_nanos()); + } + + fn try_read(buf: &mut &[u8]) -> Result<Duration> { + check_remaining(buf, 12)?; + Ok(Duration::new(buf.get_u64(), buf.get_u32())) + } + + const TYPE_ID_META: MetadataBuffer = MetadataBuffer::from_code(metadata::codes::TYPE_DURATION); +} + +// Support for passing optional values via the FFI. +// +// Optional values are currently always passed by serializing to a buffer. +// We write either a zero byte for `None`, or a one byte followed by the containing +// item for `Some`. +// +// In future we could do the same optimization as rust uses internally, where the +// `None` option is represented as a null pointer and the `Some` as a valid pointer, +// but that seems more fiddly and less safe in the short term, so it can wait. + +unsafe impl<UT, T: Lower<UT>> Lower<UT> for Option<T> { + type FfiType = RustBuffer; + + fn write(obj: Option<T>, buf: &mut Vec<u8>) { + match obj { + None => buf.put_i8(0), + Some(v) => { + buf.put_i8(1); + T::write(v, buf); + } + } + } + + fn lower(obj: Option<T>) -> RustBuffer { + Self::lower_into_rust_buffer(obj) + } + + const TYPE_ID_META: MetadataBuffer = + MetadataBuffer::from_code(metadata::codes::TYPE_OPTION).concat(T::TYPE_ID_META); +} + +unsafe impl<UT, T: Lift<UT>> Lift<UT> for Option<T> { + type FfiType = RustBuffer; + + fn try_read(buf: &mut &[u8]) -> Result<Option<T>> { + check_remaining(buf, 1)?; + Ok(match buf.get_i8() { + 0 => None, + 1 => Some(T::try_read(buf)?), + _ => bail!("unexpected tag byte for Option"), + }) + } + + fn try_lift(buf: RustBuffer) -> Result<Option<T>> { + Self::try_lift_from_rust_buffer(buf) + } + + const TYPE_ID_META: MetadataBuffer = + MetadataBuffer::from_code(metadata::codes::TYPE_OPTION).concat(T::TYPE_ID_META); +} + +// Support for passing vectors of values via the FFI. +// +// Vectors are currently always passed by serializing to a buffer. +// We write a `i32` item count followed by each item in turn. +// (It's a signed type due to limits of the JVM). +// +// Ideally we would pass `Vec<u8>` directly as a `RustBuffer` rather +// than serializing, and perhaps even pass other vector types using a +// similar struct. But that's for future work. + +unsafe impl<UT, T: Lower<UT>> Lower<UT> for Vec<T> { + type FfiType = RustBuffer; + + fn write(obj: Vec<T>, buf: &mut Vec<u8>) { + // TODO: would be nice not to panic here :-/ + let len = i32::try_from(obj.len()).unwrap(); + buf.put_i32(len); // We limit arrays to i32::MAX items + for item in obj { + <T as Lower<UT>>::write(item, buf); + } + } + + fn lower(obj: Vec<T>) -> RustBuffer { + Self::lower_into_rust_buffer(obj) + } + + const TYPE_ID_META: MetadataBuffer = + MetadataBuffer::from_code(metadata::codes::TYPE_VEC).concat(T::TYPE_ID_META); +} + +/// Support for associative arrays via the FFI - `record<u32, u64>` in UDL. +/// HashMaps are currently always passed by serializing to a buffer. +/// We write a `i32` entries count followed by each entry (string +/// key followed by the value) in turn. +/// (It's a signed type due to limits of the JVM). +unsafe impl<UT, T: Lift<UT>> Lift<UT> for Vec<T> { + type FfiType = RustBuffer; + + fn try_read(buf: &mut &[u8]) -> Result<Vec<T>> { + check_remaining(buf, 4)?; + let len = usize::try_from(buf.get_i32())?; + let mut vec = Vec::with_capacity(len); + for _ in 0..len { + vec.push(<T as Lift<UT>>::try_read(buf)?) + } + Ok(vec) + } + + fn try_lift(buf: RustBuffer) -> Result<Vec<T>> { + Self::try_lift_from_rust_buffer(buf) + } + + const TYPE_ID_META: MetadataBuffer = + MetadataBuffer::from_code(metadata::codes::TYPE_VEC).concat(T::TYPE_ID_META); +} + +unsafe impl<K, V, UT> Lower<UT> for HashMap<K, V> +where + K: Lower<UT> + std::hash::Hash + Eq, + V: Lower<UT>, +{ + type FfiType = RustBuffer; + + fn write(obj: HashMap<K, V>, buf: &mut Vec<u8>) { + // TODO: would be nice not to panic here :-/ + let len = i32::try_from(obj.len()).unwrap(); + buf.put_i32(len); // We limit HashMaps to i32::MAX entries + for (key, value) in obj { + <K as Lower<UT>>::write(key, buf); + <V as Lower<UT>>::write(value, buf); + } + } + + fn lower(obj: HashMap<K, V>) -> RustBuffer { + Self::lower_into_rust_buffer(obj) + } + + const TYPE_ID_META: MetadataBuffer = MetadataBuffer::from_code(metadata::codes::TYPE_HASH_MAP) + .concat(K::TYPE_ID_META) + .concat(V::TYPE_ID_META); +} + +unsafe impl<K, V, UT> Lift<UT> for HashMap<K, V> +where + K: Lift<UT> + std::hash::Hash + Eq, + V: Lift<UT>, +{ + type FfiType = RustBuffer; + + fn try_read(buf: &mut &[u8]) -> Result<HashMap<K, V>> { + check_remaining(buf, 4)?; + let len = usize::try_from(buf.get_i32())?; + let mut map = HashMap::with_capacity(len); + for _ in 0..len { + let key = <K as Lift<UT>>::try_read(buf)?; + let value = <V as Lift<UT>>::try_read(buf)?; + map.insert(key, value); + } + Ok(map) + } + + fn try_lift(buf: RustBuffer) -> Result<HashMap<K, V>> { + Self::try_lift_from_rust_buffer(buf) + } + + const TYPE_ID_META: MetadataBuffer = MetadataBuffer::from_code(metadata::codes::TYPE_HASH_MAP) + .concat(K::TYPE_ID_META) + .concat(V::TYPE_ID_META); +} + +derive_ffi_traits!(blanket u8); +derive_ffi_traits!(blanket i8); +derive_ffi_traits!(blanket u16); +derive_ffi_traits!(blanket i16); +derive_ffi_traits!(blanket u32); +derive_ffi_traits!(blanket i32); +derive_ffi_traits!(blanket u64); +derive_ffi_traits!(blanket i64); +derive_ffi_traits!(blanket f32); +derive_ffi_traits!(blanket f64); +derive_ffi_traits!(blanket bool); +derive_ffi_traits!(blanket String); +derive_ffi_traits!(blanket Duration); +derive_ffi_traits!(blanket SystemTime); + +// For composite types, derive LowerReturn, LiftReturn, etc, from Lift/Lower. +// +// Note that this means we don't get specialized return handling. For example, if we could return +// an `Option<Result<>>` we would always return that type directly and never throw. +derive_ffi_traits!(impl<T, UT> LowerReturn<UT> for Option<T> where Option<T>: Lower<UT>); +derive_ffi_traits!(impl<T, UT> LiftReturn<UT> for Option<T> where Option<T>: Lift<UT>); +derive_ffi_traits!(impl<T, UT> LiftRef<UT> for Option<T> where Option<T>: Lift<UT>); + +derive_ffi_traits!(impl<T, UT> LowerReturn<UT> for Vec<T> where Vec<T>: Lower<UT>); +derive_ffi_traits!(impl<T, UT> LiftReturn<UT> for Vec<T> where Vec<T>: Lift<UT>); +derive_ffi_traits!(impl<T, UT> LiftRef<UT> for Vec<T> where Vec<T>: Lift<UT>); + +derive_ffi_traits!(impl<K, V, UT> LowerReturn<UT> for HashMap<K, V> where HashMap<K, V>: Lower<UT>); +derive_ffi_traits!(impl<K, V, UT> LiftReturn<UT> for HashMap<K, V> where HashMap<K, V>: Lift<UT>); +derive_ffi_traits!(impl<K, V, UT> LiftRef<UT> for HashMap<K, V> where HashMap<K, V>: Lift<UT>); + +// For Arc we derive all the traits, but have to write it all out because we need an unsized T bound +derive_ffi_traits!(impl<T, UT> Lower<UT> for Arc<T> where Arc<T>: FfiConverter<UT>, T: ?Sized); +derive_ffi_traits!(impl<T, UT> Lift<UT> for Arc<T> where Arc<T>: FfiConverter<UT>, T: ?Sized); +derive_ffi_traits!(impl<T, UT> LowerReturn<UT> for Arc<T> where Arc<T>: Lower<UT>, T: ?Sized); +derive_ffi_traits!(impl<T, UT> LiftReturn<UT> for Arc<T> where Arc<T>: Lift<UT>, T: ?Sized); +derive_ffi_traits!(impl<T, UT> LiftRef<UT> for Arc<T> where Arc<T>: Lift<UT>, T: ?Sized); + +// Implement LowerReturn/LiftReturn for the unit type (void returns) + +unsafe impl<UT> LowerReturn<UT> for () { + type ReturnType = (); + + fn lower_return(_: ()) -> Result<Self::ReturnType, RustBuffer> { + Ok(()) + } + + const TYPE_ID_META: MetadataBuffer = MetadataBuffer::from_code(metadata::codes::TYPE_UNIT); +} + +unsafe impl<UT> LiftReturn<UT> for () { + fn lift_callback_return(_buf: RustBuffer) -> Self {} + + const TYPE_ID_META: MetadataBuffer = MetadataBuffer::from_code(metadata::codes::TYPE_UNIT); +} + +// Implement LowerReturn/LiftReturn for `Result<R, E>`. This is where we handle exceptions/Err +// results. + +unsafe impl<UT, R, E> LowerReturn<UT> for Result<R, E> +where + R: LowerReturn<UT>, + E: Lower<UT> + Error + Send + Sync + 'static, +{ + type ReturnType = R::ReturnType; + + fn lower_return(v: Self) -> Result<Self::ReturnType, RustBuffer> { + match v { + Ok(r) => R::lower_return(r), + Err(e) => Err(E::lower_into_rust_buffer(e)), + } + } + + fn handle_failed_lift(arg_name: &str, err: anyhow::Error) -> Self { + match err.downcast::<E>() { + Ok(actual_error) => Err(actual_error), + Err(ohno) => panic!("Failed to convert arg '{arg_name}': {ohno}"), + } + } + + const TYPE_ID_META: MetadataBuffer = MetadataBuffer::from_code(metadata::codes::TYPE_RESULT) + .concat(R::TYPE_ID_META) + .concat(E::TYPE_ID_META); +} + +unsafe impl<UT, R, E> LiftReturn<UT> for Result<R, E> +where + R: LiftReturn<UT>, + E: Lift<UT> + ConvertError<UT>, +{ + fn lift_callback_return(buf: RustBuffer) -> Self { + Ok(R::lift_callback_return(buf)) + } + + fn lift_callback_error(buf: RustBuffer) -> Self { + match E::try_lift_from_rust_buffer(buf) { + Ok(lifted_error) => Err(lifted_error), + Err(anyhow_error) => { + Self::handle_callback_unexpected_error(UnexpectedUniFFICallbackError { + reason: format!("Error lifting from rust buffer: {anyhow_error}"), + }) + } + } + } + + fn handle_callback_unexpected_error(e: UnexpectedUniFFICallbackError) -> Self { + Err(E::try_convert_unexpected_callback_error(e).unwrap_or_else(|e| panic!("{e}"))) + } + + const TYPE_ID_META: MetadataBuffer = MetadataBuffer::from_code(metadata::codes::TYPE_RESULT) + .concat(R::TYPE_ID_META) + .concat(E::TYPE_ID_META); +} + +unsafe impl<T, UT> LiftRef<UT> for [T] +where + T: Lift<UT>, +{ + type LiftType = Vec<T>; +} + +unsafe impl<UT> LiftRef<UT> for str { + type LiftType = String; +} diff --git a/src/ffi_converter_traits.rs b/src/ffi_converter_traits.rs new file mode 100644 index 0000000..3b5914e --- /dev/null +++ b/src/ffi_converter_traits.rs @@ -0,0 +1,466 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +//! Traits that define how to transfer values via the FFI layer. +//! +//! These traits define how to pass values over the FFI in various ways: as arguments or as return +//! values, from Rust to the foreign side and vice-versa. These traits are mainly used by the +//! proc-macro generated code. The goal is to allow the proc-macros to go from a type name to the +//! correct function for a given FFI operation. +//! +//! The traits form a sort-of tree structure from general to specific: +//! ```ignore +//! +//! [FfiConverter] +//! | +//! ----------------------------- +//! | | +//! [Lower] [Lift] +//! | | +//! | -------------- +//! | | | +//! [LowerReturn] [LiftRef] [LiftReturn] +//! ``` +//! +//! The `derive_ffi_traits` macro can be used to derive the specific traits from the general ones. +//! Here's the main ways we implement these traits: +//! +//! * For most types we implement [FfiConverter] and use [derive_ffi_traits] to implement the rest +//! * If a type can only be lifted/lowered, then we implement [Lift] or [Lower] and use +//! [derive_ffi_traits] to implement the rest +//! * If a type needs special-case handling, like `Result<>` and `()`, we implement the traits +//! directly. +//! +//! FfiConverter has a generic parameter, that's filled in with a type local to the UniFFI consumer crate. +//! This allows us to work around the Rust orphan rules for remote types. See +//! `https://mozilla.github.io/uniffi-rs/internals/lifting_and_lowering.html#code-generation-and-the-fficonverter-trait` +//! for details. +//! +//! ## Safety +//! +//! All traits are unsafe (implementing it requires `unsafe impl`) because we can't guarantee +//! that it's safe to pass your type out to foreign-language code and back again. Buggy +//! implementations of this trait might violate some assumptions made by the generated code, +//! or might not match with the corresponding code in the generated foreign-language bindings. +//! These traits should not be used directly, only in generated code, and the generated code should +//! have fixture tests to test that everything works correctly together. + +use std::{borrow::Borrow, sync::Arc}; + +use anyhow::bail; +use bytes::Buf; + +use crate::{FfiDefault, MetadataBuffer, Result, RustBuffer, UnexpectedUniFFICallbackError}; + +/// Generalized FFI conversions +/// +/// This trait is not used directly by the code generation, but implement this and calling +/// [derive_ffi_traits] is a simple way to implement all the traits that are. +/// +/// ## Safety +/// +/// All traits are unsafe (implementing it requires `unsafe impl`) because we can't guarantee +/// that it's safe to pass your type out to foreign-language code and back again. Buggy +/// implementations of this trait might violate some assumptions made by the generated code, +/// or might not match with the corresponding code in the generated foreign-language bindings. +/// These traits should not be used directly, only in generated code, and the generated code should +/// have fixture tests to test that everything works correctly together. +pub unsafe trait FfiConverter<UT>: Sized { + /// The low-level type used for passing values of this type over the FFI. + /// + /// This must be a C-compatible type (e.g. a numeric primitive, a `#[repr(C)]` struct) into + /// which values of the target rust type can be converted. + /// + /// For complex data types, we currently recommend using `RustBuffer` and serializing + /// the data for transfer. In theory it could be possible to build a matching + /// `#[repr(C)]` struct for a complex data type and pass that instead, but explicit + /// serialization is simpler and safer as a starting point. + /// + /// If a type implements multiple FFI traits, `FfiType` must be the same for all of them. + type FfiType: FfiDefault; + + /// Lower a rust value of the target type, into an FFI value of type Self::FfiType. + /// + /// This trait method is used for sending data from rust to the foreign language code, + /// by (hopefully cheaply!) converting it into something that can be passed over the FFI + /// and reconstructed on the other side. + /// + /// Note that this method takes an owned value; this allows it to transfer ownership in turn to + /// the foreign language code, e.g. by boxing the value and passing a pointer. + fn lower(obj: Self) -> Self::FfiType; + + /// Lift a rust value of the target type, from an FFI value of type Self::FfiType. + /// + /// This trait method is used for receiving data from the foreign language code in rust, + /// by (hopefully cheaply!) converting it from a low-level FFI value of type Self::FfiType + /// into a high-level rust value of the target type. + /// + /// Since we cannot statically guarantee that the foreign-language code will send valid + /// values of type Self::FfiType, this method is fallible. + fn try_lift(v: Self::FfiType) -> Result<Self>; + + /// Write a rust value into a buffer, to send over the FFI in serialized form. + /// + /// This trait method can be used for sending data from rust to the foreign language code, + /// in cases where we're not able to use a special-purpose FFI type and must fall back to + /// sending serialized bytes. + /// + /// Note that this method takes an owned value because it's transferring ownership + /// to the foreign language code via the RustBuffer. + fn write(obj: Self, buf: &mut Vec<u8>); + + /// Read a rust value from a buffer, received over the FFI in serialized form. + /// + /// This trait method can be used for receiving data from the foreign language code in rust, + /// in cases where we're not able to use a special-purpose FFI type and must fall back to + /// receiving serialized bytes. + /// + /// Since we cannot statically guarantee that the foreign-language code will send valid + /// serialized bytes for the target type, this method is fallible. + /// + /// Note the slightly unusual type here - we want a mutable reference to a slice of bytes, + /// because we want to be able to advance the start of the slice after reading an item + /// from it (but will not mutate the actual contents of the slice). + fn try_read(buf: &mut &[u8]) -> Result<Self>; + + /// Type ID metadata, serialized into a [MetadataBuffer]. + /// + /// If a type implements multiple FFI traits, `TYPE_ID_META` must be the same for all of them. + const TYPE_ID_META: MetadataBuffer; +} + +/// FfiConverter for Arc-types +/// +/// This trait gets around the orphan rule limitations, which prevent library crates from +/// implementing `FfiConverter` on an Arc. When this is implemented for T, we generate an +/// `FfiConverter` impl for Arc<T>. +/// +/// Note: There's no need for `FfiConverterBox`, since Box is a fundamental type. +/// +/// ## Safety +/// +/// All traits are unsafe (implementing it requires `unsafe impl`) because we can't guarantee +/// that it's safe to pass your type out to foreign-language code and back again. Buggy +/// implementations of this trait might violate some assumptions made by the generated code, +/// or might not match with the corresponding code in the generated foreign-language bindings. +/// These traits should not be used directly, only in generated code, and the generated code should +/// have fixture tests to test that everything works correctly together. +pub unsafe trait FfiConverterArc<UT>: Send + Sync { + type FfiType: FfiDefault; + + fn lower(obj: Arc<Self>) -> Self::FfiType; + fn try_lift(v: Self::FfiType) -> Result<Arc<Self>>; + fn write(obj: Arc<Self>, buf: &mut Vec<u8>); + fn try_read(buf: &mut &[u8]) -> Result<Arc<Self>>; + + const TYPE_ID_META: MetadataBuffer; +} + +unsafe impl<T, UT> FfiConverter<UT> for Arc<T> +where + T: FfiConverterArc<UT> + ?Sized, +{ + type FfiType = T::FfiType; + + fn lower(obj: Self) -> Self::FfiType { + T::lower(obj) + } + + fn try_lift(v: Self::FfiType) -> Result<Self> { + T::try_lift(v) + } + + fn write(obj: Self, buf: &mut Vec<u8>) { + T::write(obj, buf) + } + + fn try_read(buf: &mut &[u8]) -> Result<Self> { + T::try_read(buf) + } + + const TYPE_ID_META: MetadataBuffer = T::TYPE_ID_META; +} + +/// Lift values passed by the foreign code over the FFI into Rust values +/// +/// This is used by the code generation to handle arguments. It's usually derived from +/// [FfiConverter], except for types that only support lifting but not lowering. +/// +/// See [FfiConverter] for a discussion of the methods +/// +/// ## Safety +/// +/// All traits are unsafe (implementing it requires `unsafe impl`) because we can't guarantee +/// that it's safe to pass your type out to foreign-language code and back again. Buggy +/// implementations of this trait might violate some assumptions made by the generated code, +/// or might not match with the corresponding code in the generated foreign-language bindings. +/// These traits should not be used directly, only in generated code, and the generated code should +/// have fixture tests to test that everything works correctly together. +pub unsafe trait Lift<UT>: Sized { + type FfiType; + + fn try_lift(v: Self::FfiType) -> Result<Self>; + + fn try_read(buf: &mut &[u8]) -> Result<Self>; + + /// Convenience method + fn try_lift_from_rust_buffer(v: RustBuffer) -> Result<Self> { + let vec = v.destroy_into_vec(); + let mut buf = vec.as_slice(); + let value = Self::try_read(&mut buf)?; + match Buf::remaining(&buf) { + 0 => Ok(value), + n => bail!("junk data left in buffer after lifting (count: {n})",), + } + } + + const TYPE_ID_META: MetadataBuffer; +} + +/// Lower Rust values to pass them to the foreign code +/// +/// This is used to pass arguments to callback interfaces. It's usually derived from +/// [FfiConverter], except for types that only support lowering but not lifting. +/// +/// See [FfiConverter] for a discussion of the methods +/// +/// ## Safety +/// +/// All traits are unsafe (implementing it requires `unsafe impl`) because we can't guarantee +/// that it's safe to pass your type out to foreign-language code and back again. Buggy +/// implementations of this trait might violate some assumptions made by the generated code, +/// or might not match with the corresponding code in the generated foreign-language bindings. +/// These traits should not be used directly, only in generated code, and the generated code should +/// have fixture tests to test that everything works correctly together. +pub unsafe trait Lower<UT>: Sized { + type FfiType: FfiDefault; + + fn lower(obj: Self) -> Self::FfiType; + + fn write(obj: Self, buf: &mut Vec<u8>); + + /// Convenience method + fn lower_into_rust_buffer(obj: Self) -> RustBuffer { + let mut buf = ::std::vec::Vec::new(); + Self::write(obj, &mut buf); + RustBuffer::from_vec(buf) + } + + const TYPE_ID_META: MetadataBuffer; +} + +/// Return Rust values to the foreign code +/// +/// This is usually derived from [Lift], but we special case types like `Result<>` and `()`. +/// +/// ## Safety +/// +/// All traits are unsafe (implementing it requires `unsafe impl`) because we can't guarantee +/// that it's safe to pass your type out to foreign-language code and back again. Buggy +/// implementations of this trait might violate some assumptions made by the generated code, +/// or might not match with the corresponding code in the generated foreign-language bindings. +/// These traits should not be used directly, only in generated code, and the generated code should +/// have fixture tests to test that everything works correctly together. +pub unsafe trait LowerReturn<UT>: Sized { + /// The type that should be returned by scaffolding functions for this type. + /// + /// When derived, it's the same as `FfiType`. + type ReturnType: FfiDefault; + + /// Lower this value for scaffolding function return + /// + /// This method converts values into the `Result<>` type that [rust_call] expects. For + /// successful calls, return `Ok(lower_return)`. For errors that should be translated into + /// thrown exceptions on the foreign code, serialize the error into a RustBuffer and return + /// `Err(buf)` + fn lower_return(obj: Self) -> Result<Self::ReturnType, RustBuffer>; + + /// If possible, get a serialized error for failed argument lifts + /// + /// By default, we just panic and let `rust_call` handle things. However, for `Result<_, E>` + /// returns, if the anyhow error can be downcast to `E`, then serialize that and return it. + /// This results in the foreign code throwing a "normal" exception, rather than an unexpected + /// exception. + fn handle_failed_lift(arg_name: &str, e: anyhow::Error) -> Self { + panic!("Failed to convert arg '{arg_name}': {e}") + } + + const TYPE_ID_META: MetadataBuffer; +} + +/// Return foreign values to Rust +/// +/// This is usually derived from [Lower], but we special case types like `Result<>` and `()`. +/// +/// ## Safety +/// +/// All traits are unsafe (implementing it requires `unsafe impl`) because we can't guarantee +/// that it's safe to pass your type out to foreign-language code and back again. Buggy +/// implementations of this trait might violate some assumptions made by the generated code, +/// or might not match with the corresponding code in the generated foreign-language bindings. +/// These traits should not be used directly, only in generated code, and the generated code should +/// have fixture tests to test that everything works correctly together. +pub unsafe trait LiftReturn<UT>: Sized { + /// Lift a Rust value for a callback interface method result + fn lift_callback_return(buf: RustBuffer) -> Self; + + /// Lift a Rust value for a callback interface method error result + /// + /// This is called for "expected errors" -- the callback method returns a Result<> type and the + /// foreign code throws an exception that corresponds to the error type. + fn lift_callback_error(_buf: RustBuffer) -> Self { + panic!("Callback interface method returned unexpected error") + } + + /// Lift a Rust value for an unexpected callback interface error + /// + /// The main reason this is called is when the callback interface throws an error type that + /// doesn't match the Rust trait definition. It's also called for corner cases, like when the + /// foreign code doesn't follow the FFI contract. + /// + /// The default implementation panics unconditionally. Errors used in callback interfaces + /// handle this using the `From<UnexpectedUniFFICallbackError>` impl that the library author + /// must provide. + fn handle_callback_unexpected_error(e: UnexpectedUniFFICallbackError) -> Self { + panic!("Callback interface failure: {e}") + } + + const TYPE_ID_META: MetadataBuffer; +} + +/// Lift references +/// +/// This is usually derived from [Lift] and also implemented for the inner `T` value of smart +/// pointers. For example, if `Lift` is implemented for `Arc<T>`, then we implement this to lift +/// +/// ## Safety +/// +/// All traits are unsafe (implementing it requires `unsafe impl`) because we can't guarantee +/// that it's safe to pass your type out to foreign-language code and back again. Buggy +/// implementations of this trait might violate some assumptions made by the generated code, +/// or might not match with the corresponding code in the generated foreign-language bindings. +/// These traits should not be used directly, only in generated code, and the generated code should +/// have fixture tests to test that everything works correctly together. +/// `&T` using the Arc. +pub unsafe trait LiftRef<UT> { + type LiftType: Lift<UT> + Borrow<Self>; +} + +pub trait ConvertError<UT>: Sized { + fn try_convert_unexpected_callback_error(e: UnexpectedUniFFICallbackError) -> Result<Self>; +} + +/// Derive FFI traits +/// +/// This can be used to derive: +/// * [Lower] and [Lift] from [FfiConverter] +/// * [LowerReturn] from [Lower] +/// * [LiftReturn] and [LiftRef] from [Lift] +/// +/// Usage: +/// ```ignore +/// +/// // Derive everything from [FfiConverter] for all Uniffi tags +/// ::uniffi::derive_ffi_traits!(blanket Foo) +/// // Derive everything from [FfiConverter] for the local crate::UniFfiTag +/// ::uniffi::derive_ffi_traits!(local Foo) +/// // To derive a specific trait, write out the impl item minus the actual block +/// ::uniffi::derive_ffi_traits!(impl<T, UT> LowerReturn<UT> for Option<T>) +/// ``` +#[macro_export] +#[allow(clippy::crate_in_macro_def)] +macro_rules! derive_ffi_traits { + (blanket $ty:ty) => { + $crate::derive_ffi_traits!(impl<UT> Lower<UT> for $ty); + $crate::derive_ffi_traits!(impl<UT> Lift<UT> for $ty); + $crate::derive_ffi_traits!(impl<UT> LowerReturn<UT> for $ty); + $crate::derive_ffi_traits!(impl<UT> LiftReturn<UT> for $ty); + $crate::derive_ffi_traits!(impl<UT> LiftRef<UT> for $ty); + $crate::derive_ffi_traits!(impl<UT> ConvertError<UT> for $ty); + }; + + (local $ty:ty) => { + $crate::derive_ffi_traits!(impl Lower<crate::UniFfiTag> for $ty); + $crate::derive_ffi_traits!(impl Lift<crate::UniFfiTag> for $ty); + $crate::derive_ffi_traits!(impl LowerReturn<crate::UniFfiTag> for $ty); + $crate::derive_ffi_traits!(impl LiftReturn<crate::UniFfiTag> for $ty); + $crate::derive_ffi_traits!(impl LiftRef<crate::UniFfiTag> for $ty); + $crate::derive_ffi_traits!(impl ConvertError<crate::UniFfiTag> for $ty); + }; + + (impl $(<$($generic:ident),*>)? $(::uniffi::)? Lower<$ut:path> for $ty:ty $(where $($where:tt)*)?) => { + unsafe impl $(<$($generic),*>)* $crate::Lower<$ut> for $ty $(where $($where)*)* + { + type FfiType = <Self as $crate::FfiConverter<$ut>>::FfiType; + + fn lower(obj: Self) -> Self::FfiType { + <Self as $crate::FfiConverter<$ut>>::lower(obj) + } + + fn write(obj: Self, buf: &mut ::std::vec::Vec<u8>) { + <Self as $crate::FfiConverter<$ut>>::write(obj, buf) + } + + const TYPE_ID_META: $crate::MetadataBuffer = <Self as $crate::FfiConverter<$ut>>::TYPE_ID_META; + } + }; + + (impl $(<$($generic:ident),*>)? $(::uniffi::)? Lift<$ut:path> for $ty:ty $(where $($where:tt)*)?) => { + unsafe impl $(<$($generic),*>)* $crate::Lift<$ut> for $ty $(where $($where)*)* + { + type FfiType = <Self as $crate::FfiConverter<$ut>>::FfiType; + + fn try_lift(v: Self::FfiType) -> $crate::deps::anyhow::Result<Self> { + <Self as $crate::FfiConverter<$ut>>::try_lift(v) + } + + fn try_read(buf: &mut &[u8]) -> $crate::deps::anyhow::Result<Self> { + <Self as $crate::FfiConverter<$ut>>::try_read(buf) + } + + const TYPE_ID_META: $crate::MetadataBuffer = <Self as $crate::FfiConverter<$ut>>::TYPE_ID_META; + } + }; + + (impl $(<$($generic:ident),*>)? $(::uniffi::)? LowerReturn<$ut:path> for $ty:ty $(where $($where:tt)*)?) => { + unsafe impl $(<$($generic),*>)* $crate::LowerReturn<$ut> for $ty $(where $($where)*)* + { + type ReturnType = <Self as $crate::Lower<$ut>>::FfiType; + + fn lower_return(obj: Self) -> $crate::deps::anyhow::Result<Self::ReturnType, $crate::RustBuffer> { + Ok(<Self as $crate::Lower<$ut>>::lower(obj)) + } + + const TYPE_ID_META: $crate::MetadataBuffer =<Self as $crate::Lower<$ut>>::TYPE_ID_META; + } + }; + + (impl $(<$($generic:ident),*>)? $(::uniffi::)? LiftReturn<$ut:path> for $ty:ty $(where $($where:tt)*)?) => { + unsafe impl $(<$($generic),*>)* $crate::LiftReturn<$ut> for $ty $(where $($where)*)* + { + fn lift_callback_return(buf: $crate::RustBuffer) -> Self { + <Self as $crate::Lift<$ut>>::try_lift_from_rust_buffer(buf) + .expect("Error reading callback interface result") + } + + const TYPE_ID_META: $crate::MetadataBuffer = <Self as $crate::Lift<$ut>>::TYPE_ID_META; + } + }; + + (impl $(<$($generic:ident),*>)? $(::uniffi::)? LiftRef<$ut:path> for $ty:ty $(where $($where:tt)*)?) => { + unsafe impl $(<$($generic),*>)* $crate::LiftRef<$ut> for $ty $(where $($where)*)* + { + type LiftType = Self; + } + }; + + (impl $(<$($generic:ident),*>)? $(::uniffi::)? ConvertError<$ut:path> for $ty:ty $(where $($where:tt)*)?) => { + impl $(<$($generic),*>)* $crate::ConvertError<$ut> for $ty $(where $($where)*)* + { + fn try_convert_unexpected_callback_error(e: $crate::UnexpectedUniFFICallbackError) -> $crate::deps::anyhow::Result<Self> { + $crate::convert_unexpected_error!(e, $ty) + } + } + }; +} diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..9003b08 --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,322 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +//! # Runtime support code for uniffi +//! +//! This crate provides the small amount of runtime code that is required by the generated uniffi +//! component scaffolding in order to transfer data back and forth across the C-style FFI layer, +//! as well as some utilities for testing the generated bindings. +//! +//! The key concept here is the [`FfiConverter`] trait, which is responsible for converting between +//! a Rust type and a low-level C-style type that can be passed across the FFI: +//! +//! * How to [represent](FfiConverter::FfiType) values of the Rust type in the low-level C-style type +//! system of the FFI layer. +//! * How to ["lower"](FfiConverter::lower) values of the Rust type into an appropriate low-level +//! FFI value. +//! * How to ["lift"](FfiConverter::try_lift) low-level FFI values back into values of the Rust +//! type. +//! * How to [write](FfiConverter::write) values of the Rust type into a buffer, for cases +//! where they are part of a compound data structure that is serialized for transfer. +//! * How to [read](FfiConverter::try_read) values of the Rust type from buffer, for cases +//! where they are received as part of a compound data structure that was serialized for transfer. +//! * How to [return](FfiConverter::lower_return) values of the Rust type from scaffolding +//! functions. +//! +//! This logic encapsulates the Rust-side handling of data transfer. Each foreign-language binding +//! must also implement a matching set of data-handling rules for each data type. +//! +//! In addition to the core `FfiConverter` trait, we provide a handful of struct definitions useful +//! for passing core rust types over the FFI, such as [`RustBuffer`]. + +#![warn(rust_2018_idioms, unused_qualifications)] + +use anyhow::bail; +use bytes::buf::Buf; + +// Make Result<> public to support external impls of FfiConverter +pub use anyhow::Result; + +pub mod ffi; +mod ffi_converter_impls; +mod ffi_converter_traits; +pub mod metadata; + +pub use ffi::*; +pub use ffi_converter_traits::{ + ConvertError, FfiConverter, FfiConverterArc, Lift, LiftRef, LiftReturn, Lower, LowerReturn, +}; +pub use metadata::*; + +// Re-export the libs that we use in the generated code, +// so the consumer doesn't have to depend on them directly. +pub mod deps { + pub use anyhow; + #[cfg(feature = "tokio")] + pub use async_compat; + pub use bytes; + pub use log; + pub use static_assertions; +} + +mod panichook; + +const PACKAGE_VERSION: &str = env!("CARGO_PKG_VERSION"); + +// For the significance of this magic number 10 here, and the reason that +// it can't be a named constant, see the `check_compatible_version` function. +static_assertions::const_assert!(PACKAGE_VERSION.as_bytes().len() < 10); + +/// Check whether the uniffi runtime version is compatible a given uniffi_bindgen version. +/// +/// The result of this check may be used to ensure that generated Rust scaffolding is +/// using a compatible version of the uniffi runtime crate. It's a `const fn` so that it +/// can be used to perform such a check at compile time. +#[allow(clippy::len_zero)] +pub const fn check_compatible_version(bindgen_version: &'static str) -> bool { + // While UniFFI is still under heavy development, we require that + // the runtime support crate be precisely the same version as the + // build-time bindgen crate. + // + // What we want to achieve here is checking two strings for equality. + // Unfortunately Rust doesn't yet support calling the `&str` equals method + // in a const context. We can hack around that by doing a byte-by-byte + // comparison of the underlying bytes. + let package_version = PACKAGE_VERSION.as_bytes(); + let bindgen_version = bindgen_version.as_bytes(); + // What we want to achieve here is a loop over the underlying bytes, + // something like: + // ``` + // if package_version.len() != bindgen_version.len() { + // return false + // } + // for i in 0..package_version.len() { + // if package_version[i] != bindgen_version[i] { + // return false + // } + // } + // return true + // ``` + // Unfortunately stable Rust doesn't allow `if` or `for` in const contexts, + // so code like the above would only work in nightly. We can hack around it by + // statically asserting that the string is shorter than a certain length + // (currently 10 bytes) and then manually unrolling that many iterations of the loop. + // + // Yes, I am aware that this is horrific, but the externally-visible + // behaviour is quite nice for consumers! + package_version.len() == bindgen_version.len() + && (package_version.len() == 0 || package_version[0] == bindgen_version[0]) + && (package_version.len() <= 1 || package_version[1] == bindgen_version[1]) + && (package_version.len() <= 2 || package_version[2] == bindgen_version[2]) + && (package_version.len() <= 3 || package_version[3] == bindgen_version[3]) + && (package_version.len() <= 4 || package_version[4] == bindgen_version[4]) + && (package_version.len() <= 5 || package_version[5] == bindgen_version[5]) + && (package_version.len() <= 6 || package_version[6] == bindgen_version[6]) + && (package_version.len() <= 7 || package_version[7] == bindgen_version[7]) + && (package_version.len() <= 8 || package_version[8] == bindgen_version[8]) + && (package_version.len() <= 9 || package_version[9] == bindgen_version[9]) + && package_version.len() < 10 +} + +/// Assert that the uniffi runtime version matches an expected value. +/// +/// This is a helper hook for the generated Rust scaffolding, to produce a compile-time +/// error if the version of `uniffi_bindgen` used to generate the scaffolding was +/// incompatible with the version of `uniffi` being used at runtime. +#[macro_export] +macro_rules! assert_compatible_version { + ($v:expr $(,)?) => { + uniffi::deps::static_assertions::const_assert!(uniffi::check_compatible_version($v)); + }; +} + +/// Struct to use when we want to lift/lower/serialize types inside the `uniffi` crate. +struct UniFfiTag; + +/// A helper function to ensure we don't read past the end of a buffer. +/// +/// Rust won't actually let us read past the end of a buffer, but the `Buf` trait does not support +/// returning an explicit error in this case, and will instead panic. This is a look-before-you-leap +/// helper function to instead return an explicit error, to help with debugging. +pub fn check_remaining(buf: &[u8], num_bytes: usize) -> Result<()> { + if buf.remaining() < num_bytes { + bail!( + "not enough bytes remaining in buffer ({} < {num_bytes})", + buf.remaining(), + ); + } + Ok(()) +} + +/// Macro to implement lowering/lifting using a `RustBuffer` +/// +/// For complex types where it's too fiddly or too unsafe to convert them into a special-purpose +/// C-compatible value, you can use this trait to implement `lower()` in terms of `write()` and +/// `lift` in terms of `read()`. +/// +/// This macro implements the boilerplate needed to define `lower`, `lift` and `FFIType`. +#[macro_export] +macro_rules! ffi_converter_rust_buffer_lift_and_lower { + ($uniffi_tag:ty) => { + type FfiType = $crate::RustBuffer; + + fn lower(v: Self) -> $crate::RustBuffer { + let mut buf = ::std::vec::Vec::new(); + <Self as $crate::FfiConverter<$uniffi_tag>>::write(v, &mut buf); + $crate::RustBuffer::from_vec(buf) + } + + fn try_lift(buf: $crate::RustBuffer) -> $crate::Result<Self> { + let vec = buf.destroy_into_vec(); + let mut buf = vec.as_slice(); + let value = <Self as $crate::FfiConverter<$uniffi_tag>>::try_read(&mut buf)?; + match $crate::deps::bytes::Buf::remaining(&buf) { + 0 => Ok(value), + n => $crate::deps::anyhow::bail!( + "junk data left in buffer after lifting (count: {n})", + ), + } + } + }; +} + +/// Macro to implement `FfiConverter<T>` for a UniFfiTag using a different UniFfiTag +/// +/// This is used for external types +#[macro_export] +macro_rules! ffi_converter_forward { + // Forward a `FfiConverter` implementation + ($T:ty, $existing_impl_tag:ty, $new_impl_tag:ty) => { + ::uniffi::do_ffi_converter_forward!( + FfiConverter, + $T, + $T, + $existing_impl_tag, + $new_impl_tag + ); + + $crate::derive_ffi_traits!(local $T); + }; +} + +/// Macro to implement `FfiConverterArc<T>` for a UniFfiTag using a different UniFfiTag +/// +/// This is used for external types +#[macro_export] +macro_rules! ffi_converter_arc_forward { + ($T:ty, $existing_impl_tag:ty, $new_impl_tag:ty) => { + ::uniffi::do_ffi_converter_forward!( + FfiConverterArc, + ::std::sync::Arc<$T>, + $T, + $existing_impl_tag, + $new_impl_tag + ); + + // Note: no need to call derive_ffi_traits! because there is a blanket impl for all Arc<T> + }; +} + +// Generic code between the two macros above +#[doc(hidden)] +#[macro_export] +macro_rules! do_ffi_converter_forward { + ($trait:ident, $rust_type:ty, $T:ty, $existing_impl_tag:ty, $new_impl_tag:ty) => { + unsafe impl $crate::$trait<$new_impl_tag> for $T { + type FfiType = <$T as $crate::$trait<$existing_impl_tag>>::FfiType; + + fn lower(obj: $rust_type) -> Self::FfiType { + <$T as $crate::$trait<$existing_impl_tag>>::lower(obj) + } + + fn try_lift(v: Self::FfiType) -> $crate::Result<$rust_type> { + <$T as $crate::$trait<$existing_impl_tag>>::try_lift(v) + } + + fn write(obj: $rust_type, buf: &mut Vec<u8>) { + <$T as $crate::$trait<$existing_impl_tag>>::write(obj, buf) + } + + fn try_read(buf: &mut &[u8]) -> $crate::Result<$rust_type> { + <$T as $crate::$trait<$existing_impl_tag>>::try_read(buf) + } + + const TYPE_ID_META: ::uniffi::MetadataBuffer = + <$T as $crate::$trait<$existing_impl_tag>>::TYPE_ID_META; + } + }; +} + +#[cfg(test)] +mod test { + use super::{FfiConverter, UniFfiTag}; + use std::time::{Duration, SystemTime}; + + #[test] + fn timestamp_roundtrip_post_epoch() { + let expected = SystemTime::UNIX_EPOCH + Duration::new(100, 100); + let result = + <SystemTime as FfiConverter<UniFfiTag>>::try_lift(<SystemTime as FfiConverter< + UniFfiTag, + >>::lower(expected)) + .expect("Failed to lift!"); + assert_eq!(expected, result) + } + + #[test] + fn timestamp_roundtrip_pre_epoch() { + let expected = SystemTime::UNIX_EPOCH - Duration::new(100, 100); + let result = + <SystemTime as FfiConverter<UniFfiTag>>::try_lift(<SystemTime as FfiConverter< + UniFfiTag, + >>::lower(expected)) + .expect("Failed to lift!"); + assert_eq!( + expected, result, + "Expected results after lowering and lifting to be equal" + ) + } +} + +#[cfg(test)] +pub mod test_util { + use std::{error::Error, fmt}; + + use super::*; + + #[derive(Clone, Debug, PartialEq, Eq)] + pub struct TestError(pub String); + + // Use FfiConverter to simplify lifting TestError out of RustBuffer to check it + unsafe impl<UT> FfiConverter<UT> for TestError { + ffi_converter_rust_buffer_lift_and_lower!(UniFfiTag); + + fn write(obj: TestError, buf: &mut Vec<u8>) { + <String as FfiConverter<UniFfiTag>>::write(obj.0, buf); + } + + fn try_read(buf: &mut &[u8]) -> Result<TestError> { + <String as FfiConverter<UniFfiTag>>::try_read(buf).map(TestError) + } + + // Use a dummy value here since we don't actually need TYPE_ID_META + const TYPE_ID_META: MetadataBuffer = MetadataBuffer::new(); + } + + impl fmt::Display for TestError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", self.0) + } + } + + impl Error for TestError {} + + impl<T: Into<String>> From<T> for TestError { + fn from(v: T) -> Self { + Self(v.into()) + } + } + + derive_ffi_traits!(blanket TestError); +} diff --git a/src/metadata.rs b/src/metadata.rs new file mode 100644 index 0000000..934d09c --- /dev/null +++ b/src/metadata.rs @@ -0,0 +1,277 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +//! Pack UniFFI interface metadata into byte arrays +//! +//! In order to generate foreign bindings, we store interface metadata inside the library file +//! using exported static byte arrays. The foreign bindings code reads that metadata from the +//! library files and generates bindings based on that. +//! +//! The metadata static variables are generated by the proc-macros, which is an issue because the +//! proc-macros don't have knowledge of the entire interface -- they can only see the item they're +//! wrapping. For example, when a proc-macro sees a type name, it doesn't know anything about the +//! actual type: it could be a Record, an Enum, or even a type alias for a `Vec<>`/`Result<>`. +//! +//! This module helps bridge the gap by providing tools that allow the proc-macros to generate code +//! to encode the interface metadata: +//! - A set of const functions to build up metadata buffers with const expressions +//! - The `export_static_metadata_var!` macro, which creates the static variable from a const metadata +//! buffer. +//! - The `FfiConverter::TYPE_ID_META` const which encodes an identifier for that type in a +//! metadata buffer. +//! +//! `uniffi_bindgen::macro_metadata` contains the code to read the metadata from a library file. +//! `fixtures/metadata` has the tests. + +/// Metadata constants, make sure to keep this in sync with copy in `uniffi_meta::reader` +pub mod codes { + // Top-level metadata item codes + pub const FUNC: u8 = 0; + pub const METHOD: u8 = 1; + pub const RECORD: u8 = 2; + pub const ENUM: u8 = 3; + pub const INTERFACE: u8 = 4; + pub const NAMESPACE: u8 = 6; + pub const CONSTRUCTOR: u8 = 7; + pub const UDL_FILE: u8 = 8; + pub const CALLBACK_INTERFACE: u8 = 9; + pub const TRAIT_METHOD: u8 = 10; + pub const UNIFFI_TRAIT: u8 = 11; + pub const TRAIT_INTERFACE: u8 = 12; + pub const CALLBACK_TRAIT_INTERFACE: u8 = 13; + pub const UNKNOWN: u8 = 255; + + // Type codes + pub const TYPE_U8: u8 = 0; + pub const TYPE_U16: u8 = 1; + pub const TYPE_U32: u8 = 2; + pub const TYPE_U64: u8 = 3; + pub const TYPE_I8: u8 = 4; + pub const TYPE_I16: u8 = 5; + pub const TYPE_I32: u8 = 6; + pub const TYPE_I64: u8 = 7; + pub const TYPE_F32: u8 = 8; + pub const TYPE_F64: u8 = 9; + pub const TYPE_BOOL: u8 = 10; + pub const TYPE_STRING: u8 = 11; + pub const TYPE_OPTION: u8 = 12; + pub const TYPE_RECORD: u8 = 13; + pub const TYPE_ENUM: u8 = 14; + // 15 no longer used. + pub const TYPE_INTERFACE: u8 = 16; + pub const TYPE_VEC: u8 = 17; + pub const TYPE_HASH_MAP: u8 = 18; + pub const TYPE_SYSTEM_TIME: u8 = 19; + pub const TYPE_DURATION: u8 = 20; + pub const TYPE_CALLBACK_INTERFACE: u8 = 21; + pub const TYPE_CUSTOM: u8 = 22; + pub const TYPE_RESULT: u8 = 23; + pub const TYPE_TRAIT_INTERFACE: u8 = 24; + pub const TYPE_CALLBACK_TRAIT_INTERFACE: u8 = 25; + pub const TYPE_UNIT: u8 = 255; + + // Literal codes for LiteralMetadata - note that we don't support + // all variants in the "emit/reader" context. + pub const LIT_STR: u8 = 0; + pub const LIT_INT: u8 = 1; + pub const LIT_FLOAT: u8 = 2; + pub const LIT_BOOL: u8 = 3; + pub const LIT_NULL: u8 = 4; +} + +const BUF_SIZE: usize = 4096; + +// This struct is a kludge around the fact that Rust const generic support doesn't quite handle our +// needs. +// +// We'd like to have code like this in `FfiConverter`: +// +// ``` +// const TYPE_ID_META_SIZE: usize; +// const TYPE_ID_META: [u8, Self::TYPE_ID_META_SIZE]; +// ``` +// +// This would define a metadata buffer, correctly size for the data needed. However, associated +// consts as generic params aren't supported yet. +// +// To work around this, we use `const MetadataBuffer` values, which contain fixed-sized buffers +// with enough capacity to store our largest metadata arrays. Since the `MetadataBuffer` values +// are const, they're only stored at compile time and the extra bytes don't end up contributing to +// the final binary size. This was tested on Rust `1.66.0` with `--release` by increasing +// `BUF_SIZE` and checking the compiled library sizes. +#[derive(Debug)] +pub struct MetadataBuffer { + pub bytes: [u8; BUF_SIZE], + pub size: usize, +} + +impl MetadataBuffer { + pub const fn new() -> Self { + Self { + bytes: [0; BUF_SIZE], + size: 0, + } + } + + pub const fn from_code(value: u8) -> Self { + Self::new().concat_value(value) + } + + // Concatenate another buffer to this one. + // + // This consumes self, which is convenient for the proc-macro code and also allows us to avoid + // allocated an extra buffer. + pub const fn concat(mut self, other: MetadataBuffer) -> MetadataBuffer { + assert!(self.size + other.size <= BUF_SIZE); + // It would be nice to use `copy_from_slice()`, but that's not allowed in const functions + // as of Rust 1.66. + let mut i = 0; + while i < other.size { + self.bytes[self.size] = other.bytes[i]; + self.size += 1; + i += 1; + } + self + } + + // Concatenate a `u8` value to this buffer + // + // This consumes self, which is convenient for the proc-macro code and also allows us to avoid + // allocated an extra buffer. + pub const fn concat_value(mut self, value: u8) -> Self { + assert!(self.size < BUF_SIZE); + self.bytes[self.size] = value; + self.size += 1; + self + } + + // Concatenate a `u32` value to this buffer + // + // This consumes self, which is convenient for the proc-macro code and also allows us to avoid + // allocated an extra buffer. + pub const fn concat_u32(mut self, value: u32) -> Self { + assert!(self.size + 4 <= BUF_SIZE); + // store the value as little-endian + self.bytes[self.size] = value as u8; + self.bytes[self.size + 1] = (value >> 8) as u8; + self.bytes[self.size + 2] = (value >> 16) as u8; + self.bytes[self.size + 3] = (value >> 24) as u8; + self.size += 4; + self + } + + // Concatenate a `bool` value to this buffer + // + // This consumes self, which is convenient for the proc-macro code and also allows us to avoid + // allocated an extra buffer. + pub const fn concat_bool(self, value: bool) -> Self { + self.concat_value(value as u8) + } + + // Option<bool> + pub const fn concat_option_bool(self, value: Option<bool>) -> Self { + self.concat_value(match value { + None => 0, + Some(false) => 1, + Some(true) => 2, + }) + } + + // Concatenate a string to this buffer. The maximum string length is 255 bytes. For longer strings, + // use `concat_long_str()`. + // + // Strings are encoded as a `u8` length, followed by the utf8 data. + // + // This consumes self, which is convenient for the proc-macro code and also allows us to avoid + // allocated an extra buffer. + pub const fn concat_str(mut self, string: &str) -> Self { + assert!(string.len() < 256); + assert!(self.size + string.len() < BUF_SIZE); + self.bytes[self.size] = string.len() as u8; + self.size += 1; + let bytes = string.as_bytes(); + let mut i = 0; + while i < bytes.len() { + self.bytes[self.size] = bytes[i]; + self.size += 1; + i += 1; + } + self + } + + // Concatenate a longer string to this buffer. + // + // Strings are encoded as a `u16` length, followed by the utf8 data. + // + // This consumes self, which is convenient for the proc-macro code and also allows us to avoid + // allocated an extra buffer. + pub const fn concat_long_str(mut self, string: &str) -> Self { + assert!(self.size + string.len() + 1 < BUF_SIZE); + let [lo, hi] = (string.len() as u16).to_le_bytes(); + self.bytes[self.size] = lo; + self.bytes[self.size + 1] = hi; + self.size += 2; + let bytes = string.as_bytes(); + let mut i = 0; + while i < bytes.len() { + self.bytes[self.size] = bytes[i]; + self.size += 1; + i += 1; + } + self + } + + // Create an array from this MetadataBuffer + // + // SIZE should always be `self.size`. This is part of the kludge to hold us over until Rust + // gets better const generic support. + pub const fn into_array<const SIZE: usize>(self) -> [u8; SIZE] { + let mut result: [u8; SIZE] = [0; SIZE]; + let mut i = 0; + while i < SIZE { + result[i] = self.bytes[i]; + i += 1; + } + result + } + + // Create a checksum from this MetadataBuffer + // + // This is used by the bindings code to verify that the library they link to is the same one + // that the bindings were generated from. + pub const fn checksum(&self) -> u16 { + calc_checksum(&self.bytes, self.size) + } +} + +impl AsRef<[u8]> for MetadataBuffer { + fn as_ref(&self) -> &[u8] { + &self.bytes[..self.size] + } +} + +// Create a checksum for a MetadataBuffer +// +// This is used by the bindings code to verify that the library they link to is the same one +// that the bindings were generated from. +pub const fn checksum_metadata(buf: &[u8]) -> u16 { + calc_checksum(buf, buf.len()) +} + +const fn calc_checksum(bytes: &[u8], size: usize) -> u16 { + // Taken from the fnv_hash() function from the FNV crate (https://github.com/servo/rust-fnv/blob/master/lib.rs). + // fnv_hash() hasn't been released in a version yet. + const INITIAL_STATE: u64 = 0xcbf29ce484222325; + const PRIME: u64 = 0x100000001b3; + + let mut hash = INITIAL_STATE; + let mut i = 0; + while i < size { + hash ^= bytes[i] as u64; + hash = hash.wrapping_mul(PRIME); + i += 1; + } + // Convert the 64-bit hash to a 16-bit hash by XORing everything together + (hash ^ (hash >> 16) ^ (hash >> 32) ^ (hash >> 48)) as u16 +} diff --git a/src/panichook.rs b/src/panichook.rs new file mode 100644 index 0000000..ef0ab86 --- /dev/null +++ b/src/panichook.rs @@ -0,0 +1,34 @@ +/// Initialize our panic handling hook to optionally log panics +#[cfg(feature = "log_panics")] +pub fn ensure_setup() { + use std::sync::Once; + static INIT_BACKTRACES: Once = Once::new(); + INIT_BACKTRACES.call_once(move || { + #[cfg(all(feature = "log_backtraces", not(target_os = "android")))] + { + std::env::set_var("RUST_BACKTRACE", "1"); + } + // Turn on a panic hook which logs both backtraces and the panic + // "Location" (file/line). We do both in case we've been stripped, + // ). + std::panic::set_hook(Box::new(move |panic_info| { + let (file, line) = if let Some(loc) = panic_info.location() { + (loc.file(), loc.line()) + } else { + // Apparently this won't happen but rust has reserved the + // ability to start returning None from location in some cases + // in the future. + ("<unknown>", 0) + }; + log::error!("### Rust `panic!` hit at file '{file}', line {line}"); + #[cfg(all(feature = "log_backtraces", not(target_os = "android")))] + { + log::error!(" Complete stack trace:\n{:?}", backtrace::Backtrace::new()); + } + })); + }); +} + +/// Initialize our panic handling hook to optionally log panics +#[cfg(not(feature = "log_panics"))] +pub fn ensure_setup() {} |