diff options
Diffstat (limited to 'third_party/rust/uniffi_bindgen/src/macro_metadata')
3 files changed, 361 insertions, 0 deletions
diff --git a/third_party/rust/uniffi_bindgen/src/macro_metadata/ci.rs b/third_party/rust/uniffi_bindgen/src/macro_metadata/ci.rs new file mode 100644 index 0000000000..7ce6c3a70b --- /dev/null +++ b/third_party/rust/uniffi_bindgen/src/macro_metadata/ci.rs @@ -0,0 +1,145 @@ +/* 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::interface::{CallbackInterface, ComponentInterface, Enum, Record, Type}; +use anyhow::{bail, Context}; +use uniffi_meta::{ + create_metadata_groups, group_metadata, EnumMetadata, ErrorMetadata, Metadata, MetadataGroup, +}; + +/// Add Metadata items to the ComponentInterface +/// +/// This function exists to support the transition period where the `uniffi::export` macro can only +/// handle some components. This means that crates need to continue using UDL files to define the +/// parts of the components that aren't supported yet. +/// +/// To make things work, we generate a `ComponentInterface` from the UDL file, then combine it with +/// the `Metadata` items that the macro creates. +pub fn add_to_ci( + iface: &mut ComponentInterface, + metadata_items: Vec<Metadata>, +) -> anyhow::Result<()> { + let mut group_map = create_metadata_groups(&metadata_items); + group_metadata(&mut group_map, metadata_items)?; + for group in group_map.into_values() { + if group.items.is_empty() { + continue; + } + if group.namespace.name != iface.namespace() { + let crate_name = group.namespace.crate_name; + bail!("Found metadata items from crate `{crate_name}`. Use the `--library` to generate bindings for multiple crates") + } + add_group_to_ci(iface, group)?; + } + + Ok(()) +} + +/// Add items from a MetadataGroup to a component interface +pub fn add_group_to_ci(iface: &mut ComponentInterface, group: MetadataGroup) -> anyhow::Result<()> { + if group.namespace.name != iface.namespace() { + bail!( + "Namespace mismatch: {} - {}", + group.namespace.name, + iface.namespace() + ); + } + + for item in group.items { + add_item_to_ci(iface, item)? + } + + iface + .derive_ffi_funcs() + .context("Failed to derive FFI functions")?; + iface + .check_consistency() + .context("ComponentInterface consistency error")?; + Ok(()) +} + +fn add_enum_to_ci( + iface: &mut ComponentInterface, + meta: EnumMetadata, + is_flat: bool, +) -> anyhow::Result<()> { + let ty = Type::Enum { + name: meta.name.clone(), + module_path: meta.module_path.clone(), + }; + iface.types.add_known_type(&ty)?; + + let enum_ = Enum::try_from_meta(meta, is_flat)?; + iface.add_enum_definition(enum_)?; + Ok(()) +} + +fn add_item_to_ci(iface: &mut ComponentInterface, item: Metadata) -> anyhow::Result<()> { + match item { + Metadata::Namespace(_) => unreachable!(), + Metadata::UdlFile(_) => (), + Metadata::Func(meta) => { + iface.add_function_definition(meta.into())?; + } + Metadata::Constructor(meta) => { + iface.add_constructor_meta(meta)?; + } + Metadata::Method(meta) => { + iface.add_method_meta(meta)?; + } + Metadata::Record(meta) => { + let ty = Type::Record { + name: meta.name.clone(), + module_path: meta.module_path.clone(), + }; + iface.types.add_known_type(&ty)?; + let record: Record = meta.try_into()?; + iface.add_record_definition(record)?; + } + Metadata::Enum(meta) => { + let flat = meta.variants.iter().all(|v| v.fields.is_empty()); + add_enum_to_ci(iface, meta, flat)?; + } + Metadata::Object(meta) => { + iface.types.add_known_type(&Type::Object { + module_path: meta.module_path.clone(), + name: meta.name.clone(), + imp: meta.imp, + })?; + iface.add_object_meta(meta)?; + } + Metadata::UniffiTrait(meta) => { + iface.add_uniffitrait_meta(meta)?; + } + Metadata::CallbackInterface(meta) => { + iface.types.add_known_type(&Type::CallbackInterface { + module_path: meta.module_path.clone(), + name: meta.name.clone(), + })?; + iface.add_callback_interface_definition(CallbackInterface::new( + meta.name, + meta.module_path, + )); + } + Metadata::TraitMethod(meta) => { + iface.add_trait_method_meta(meta)?; + } + Metadata::Error(meta) => { + iface.note_name_used_as_error(meta.name()); + match meta { + ErrorMetadata::Enum { enum_, is_flat } => { + add_enum_to_ci(iface, enum_, is_flat)?; + } + }; + } + Metadata::CustomType(meta) => { + iface.types.add_known_type(&Type::Custom { + module_path: meta.module_path.clone(), + name: meta.name, + builtin: Box::new(meta.builtin), + })?; + } + } + Ok(()) +} diff --git a/third_party/rust/uniffi_bindgen/src/macro_metadata/extract.rs b/third_party/rust/uniffi_bindgen/src/macro_metadata/extract.rs new file mode 100644 index 0000000000..25b5ef17ba --- /dev/null +++ b/third_party/rust/uniffi_bindgen/src/macro_metadata/extract.rs @@ -0,0 +1,192 @@ +/* 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 anyhow::{bail, Context}; +use camino::Utf8Path; +use fs_err as fs; +use goblin::{ + archive::Archive, + elf::Elf, + mach::{segment::Section, symbols, Mach, MachO, SingleArch}, + pe::PE, + Object, +}; +use std::collections::HashSet; +use uniffi_meta::Metadata; + +/// Extract metadata written by the `uniffi::export` macro from a library file +/// +/// In addition to generating the scaffolding, that macro and also encodes the +/// `uniffi_meta::Metadata` for the components which can be used to generate the bindings side of +/// the interface. +pub fn extract_from_library(path: &Utf8Path) -> anyhow::Result<Vec<Metadata>> { + extract_from_bytes(&fs::read(path)?) +} + +fn extract_from_bytes(file_data: &[u8]) -> anyhow::Result<Vec<Metadata>> { + match Object::parse(file_data)? { + Object::Elf(elf) => extract_from_elf(elf, file_data), + Object::PE(pe) => extract_from_pe(pe, file_data), + Object::Mach(mach) => extract_from_mach(mach, file_data), + Object::Archive(archive) => extract_from_archive(archive, file_data), + Object::Unknown(_) => bail!("Unknown library format"), + } +} + +pub fn extract_from_elf(elf: Elf<'_>, file_data: &[u8]) -> anyhow::Result<Vec<Metadata>> { + let mut extracted = ExtractedItems::new(); + let iter = elf + .syms + .iter() + .filter_map(|sym| elf.section_headers.get(sym.st_shndx).map(|sh| (sym, sh))); + + for (sym, sh) in iter { + let name = elf + .strtab + .get_at(sym.st_name) + .context("Error getting symbol name")?; + if is_metadata_symbol(name) { + // Offset relative to the start of the section. + let section_offset = sym.st_value - sh.sh_addr; + // Offset relative to the start of the file contents + extracted.extract_item(name, file_data, (sh.sh_offset + section_offset) as usize)?; + } + } + Ok(extracted.into_metadata()) +} + +pub fn extract_from_pe(pe: PE<'_>, file_data: &[u8]) -> anyhow::Result<Vec<Metadata>> { + let mut extracted = ExtractedItems::new(); + for export in pe.exports { + if let Some(name) = export.name { + if is_metadata_symbol(name) { + extracted.extract_item( + name, + file_data, + export.offset.context("Error getting symbol offset")?, + )?; + } + } + } + Ok(extracted.into_metadata()) +} + +pub fn extract_from_mach(mach: Mach<'_>, file_data: &[u8]) -> anyhow::Result<Vec<Metadata>> { + match mach { + Mach::Binary(macho) => extract_from_macho(macho, file_data), + // Multi-binary library, just extract the first one + Mach::Fat(multi_arch) => match multi_arch.get(0)? { + SingleArch::MachO(macho) => extract_from_macho(macho, file_data), + SingleArch::Archive(archive) => extract_from_archive(archive, file_data), + }, + } +} + +pub fn extract_from_macho(macho: MachO<'_>, file_data: &[u8]) -> anyhow::Result<Vec<Metadata>> { + let mut sections: Vec<Section> = Vec::new(); + for sects in macho.segments.sections() { + sections.extend(sects.map(|r| r.expect("section").0)); + } + let mut extracted = ExtractedItems::new(); + sections.sort_by_key(|s| s.addr); + + // Iterate through the symbols. This picks up symbols from the .o files embedded in a Darwin + // archive. + for (name, nlist) in macho.symbols().flatten() { + // Check that the symbol: + // - Is global (exported) + // - Has type=N_SECT (it's regular data as opposed to something like + // "undefined" or "indirect") + // - Has a metadata symbol name + if nlist.is_global() && nlist.get_type() == symbols::N_SECT && is_metadata_symbol(name) { + let section = §ions[nlist.n_sect]; + // `nlist.n_value` is an address, so we can calculating the offset inside the section + // using the difference between that and `section.addr` + let offset = section.offset as usize + nlist.n_value as usize - section.addr as usize; + extracted.extract_item(name, file_data, offset)?; + } + } + + // Iterate through the exports. This picks up symbols from .dylib files. + for export in macho.exports()? { + let name = &export.name; + if is_metadata_symbol(name) { + extracted.extract_item(name, file_data, export.offset as usize)?; + } + } + Ok(extracted.into_metadata()) +} + +pub fn extract_from_archive( + archive: Archive<'_>, + file_data: &[u8], +) -> anyhow::Result<Vec<Metadata>> { + // Store the names of archive members that have metadata symbols in them + let mut members_to_check: HashSet<&str> = HashSet::new(); + for (member_name, _, symbols) in archive.summarize() { + for name in symbols { + if is_metadata_symbol(name) { + members_to_check.insert(member_name); + } + } + } + + let mut items = vec![]; + for member_name in members_to_check { + items.append( + &mut extract_from_bytes( + archive + .extract(member_name, file_data) + .with_context(|| format!("Failed to extract archive member `{member_name}`"))?, + ) + .with_context(|| { + format!("Failed to extract data from archive member `{member_name}`") + })?, + ); + } + Ok(items) +} + +/// Container for extracted metadata items +#[derive(Default)] +struct ExtractedItems { + items: Vec<Metadata>, + /// symbol names for the extracted items, we use this to ensure that we don't extract the same + /// symbol twice + names: HashSet<String>, +} + +impl ExtractedItems { + fn new() -> Self { + Self::default() + } + + fn extract_item(&mut self, name: &str, file_data: &[u8], offset: usize) -> anyhow::Result<()> { + if self.names.contains(name) { + // Already extracted this item + return Ok(()); + } + + // Use the file data starting from offset, without specifying the end position. We don't + // always know the end position, because goblin reports the symbol size as 0 for PE and + // MachO files. + // + // This works fine, because `MetadataReader` knows when the serialized data is terminated + // and will just ignore the trailing data. + let data = &file_data[offset..]; + self.items.push(Metadata::read(data)?); + self.names.insert(name.to_string()); + Ok(()) + } + + fn into_metadata(self) -> Vec<Metadata> { + self.items + } +} + +fn is_metadata_symbol(name: &str) -> bool { + // Skip the "_" char that Darwin prepends, if present + let name = name.strip_prefix('_').unwrap_or(name); + name.starts_with("UNIFFI_META") +} diff --git a/third_party/rust/uniffi_bindgen/src/macro_metadata/mod.rs b/third_party/rust/uniffi_bindgen/src/macro_metadata/mod.rs new file mode 100644 index 0000000000..bc5a0a790f --- /dev/null +++ b/third_party/rust/uniffi_bindgen/src/macro_metadata/mod.rs @@ -0,0 +1,24 @@ +/* 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 super::ComponentInterface; +use anyhow::Context; +use camino::Utf8Path; + +mod ci; +mod extract; + +pub use ci::{add_group_to_ci, add_to_ci}; +pub use extract::extract_from_library; + +pub fn add_to_ci_from_library( + iface: &mut ComponentInterface, + library_path: &Utf8Path, +) -> anyhow::Result<()> { + add_to_ci( + iface, + extract_from_library(library_path).context("Failed to extract proc-macro metadata")?, + ) + .context("Failed to add proc-macro metadata to ComponentInterface") +} |