diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 19:33:14 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 19:33:14 +0000 |
commit | 36d22d82aa202bb199967e9512281e9a53db42c9 (patch) | |
tree | 105e8c98ddea1c1e4784a60a5a6410fa416be2de /third_party/rust/uniffi_bindgen/src/macro_metadata | |
parent | Initial commit. (diff) | |
download | firefox-esr-36d22d82aa202bb199967e9512281e9a53db42c9.tar.xz firefox-esr-36d22d82aa202bb199967e9512281e9a53db42c9.zip |
Adding upstream version 115.7.0esr.upstream/115.7.0esrupstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'third_party/rust/uniffi_bindgen/src/macro_metadata')
3 files changed, 304 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..b6fe94dd20 --- /dev/null +++ b/third_party/rust/uniffi_bindgen/src/macro_metadata/ci.rs @@ -0,0 +1,100 @@ +/* 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::{ComponentInterface, Enum, Error, Record, Type}; +use anyhow::anyhow; +use uniffi_meta::Metadata; + +/// 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<()> { + for item in metadata_items { + let (item_desc, crate_name) = match &item { + Metadata::Func(meta) => ( + format!("function `{}`", meta.name), + meta.module_path.first().unwrap(), + ), + Metadata::Method(meta) => ( + format!("method `{}.{}`", meta.self_name, meta.name), + meta.module_path.first().unwrap(), + ), + Metadata::Record(meta) => ( + format!("record `{}`", meta.name), + meta.module_path.first().unwrap(), + ), + Metadata::Enum(meta) => ( + format!("enum `{}`", meta.name), + meta.module_path.first().unwrap(), + ), + Metadata::Object(meta) => ( + format!("object `{}`", meta.name), + meta.module_path.first().unwrap(), + ), + Metadata::Error(meta) => ( + format!("error `{}`", meta.name), + meta.module_path.first().unwrap(), + ), + }; + + let ns = iface.namespace(); + if crate_name != ns { + return Err(anyhow!("Found {item_desc} from crate `{crate_name}`.") + .context(format!( + "Main crate is expected to be named `{ns}` based on the UDL namespace." + )) + .context("Mixing symbols from multiple crates is not supported yet.")); + } + + match item { + Metadata::Func(meta) => { + iface.add_fn_meta(meta)?; + } + Metadata::Method(meta) => { + iface.add_method_meta(meta); + } + Metadata::Record(meta) => { + let ty = Type::Record(meta.name.clone()); + iface.types.add_known_type(&ty)?; + iface.types.add_type_definition(&meta.name, ty)?; + + let record: Record = meta.into(); + iface.add_record_definition(record)?; + } + Metadata::Enum(meta) => { + let ty = Type::Enum(meta.name.clone()); + iface.types.add_known_type(&ty)?; + iface.types.add_type_definition(&meta.name, ty)?; + + let enum_: Enum = meta.into(); + iface.add_enum_definition(enum_)?; + } + Metadata::Object(meta) => { + iface.add_object_free_fn(meta); + } + Metadata::Error(meta) => { + let ty = Type::Error(meta.name.clone()); + iface.types.add_known_type(&ty)?; + iface.types.add_type_definition(&meta.name, ty)?; + + let error: Error = meta.into(); + iface.add_error_definition(error)?; + } + } + } + + iface.resolve_types()?; + iface.derive_ffi_funcs()?; + iface.check_consistency()?; + + 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..3de10bd087 --- /dev/null +++ b/third_party/rust/uniffi_bindgen/src/macro_metadata/extract.rs @@ -0,0 +1,185 @@ +/* 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)?, + )?); + } + 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 bincode knows when the serialized data is terminated and will + // just ignore the trailing data. + let data = &file_data[offset..]; + self.items.push(bincode::deserialize::<Metadata>(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..d303418ece --- /dev/null +++ b/third_party/rust/uniffi_bindgen/src/macro_metadata/mod.rs @@ -0,0 +1,19 @@ +/* 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 camino::Utf8Path; + +mod ci; +mod extract; + +pub use 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)?) +} |