summaryrefslogtreecommitdiffstats
path: root/third_party/rust/uniffi_bindgen/src/macro_metadata
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 19:33:14 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 19:33:14 +0000
commit36d22d82aa202bb199967e9512281e9a53db42c9 (patch)
tree105e8c98ddea1c1e4784a60a5a6410fa416be2de /third_party/rust/uniffi_bindgen/src/macro_metadata
parentInitial commit. (diff)
downloadfirefox-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')
-rw-r--r--third_party/rust/uniffi_bindgen/src/macro_metadata/ci.rs100
-rw-r--r--third_party/rust/uniffi_bindgen/src/macro_metadata/extract.rs185
-rw-r--r--third_party/rust/uniffi_bindgen/src/macro_metadata/mod.rs19
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 = &sections[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)?)
+}