Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[wasm-metadata] re-use metadata fields between modules and components #1943

Merged
merged 4 commits into from
Dec 9, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions crates/wasm-metadata/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ pub use add_metadata::AddMetadata;
pub use metadata::Metadata;
pub use names::{ComponentNames, ModuleNames};
pub use oci_annotations::{Author, Description, Licenses, Source};
pub use payload::Payload;
pub use producers::{Producers, ProducersField};

pub(crate) use rewrite::rewrite_wasm;
Expand All @@ -14,6 +15,7 @@ mod add_metadata;
mod metadata;
mod names;
mod oci_annotations;
mod payload;
mod producers;
mod rewrite;

Expand Down
249 changes: 18 additions & 231 deletions crates/wasm-metadata/src/metadata.rs
Original file line number Diff line number Diff line change
@@ -1,237 +1,24 @@
use anyhow::Result;
use serde_derive::Serialize;
use std::fmt;
use std::ops::Range;
use wasmparser::{KnownCustom, Parser, Payload::*};

use crate::{Author, ComponentNames, Description, Licenses, ModuleNames, Producers, Source};
use crate::{Author, Description, Licenses, Producers, Source};

/// A tree of the metadata found in a WebAssembly binary.
#[derive(Debug, Serialize)]
/// Metadata associated with a Wasm Component or Module
#[derive(Debug, Serialize, Default)]
#[serde(rename_all = "lowercase")]
pub enum Metadata {
/// Metadata found inside a WebAssembly component.
Component {
/// The component name, if any. Found in the component-name section.
name: Option<String>,
/// The component's producers section, if any.
producers: Option<Producers>,
/// The component's author section, if any.
author: Option<Author>,
/// Human-readable description of the binary
description: Option<Description>,
/// License(s) under which contained software is distributed as an SPDX License Expression.
licenses: Option<Licenses>,
/// URL to get source code for building the image
source: Option<Source>,
/// All child modules and components inside the component.
children: Vec<Box<Metadata>>,
/// Byte range of the module in the parent binary
range: Range<usize>,
},
/// Metadata found inside a WebAssembly module.
Module {
/// The module name, if any. Found in the name section.
name: Option<String>,
/// The module's producers section, if any.
producers: Option<Producers>,
/// The component's author section, if any.
author: Option<Author>,
/// Human-readable description of the binary
description: Option<Description>,
/// License(s) under which contained software is distributed as an SPDX License Expression.
licenses: Option<Licenses>,
/// URL to get source code for building the image
source: Option<Source>,
/// Byte range of the module in the parent binary
range: Range<usize>,
},
}

impl Metadata {
/// Parse metadata from a WebAssembly binary. Supports both core WebAssembly modules, and
/// WebAssembly components.
pub fn from_binary(input: &[u8]) -> Result<Self> {
let mut metadata = Vec::new();

for payload in Parser::new(0).parse_all(&input) {
match payload? {
Version { encoding, .. } => {
if metadata.is_empty() {
match encoding {
wasmparser::Encoding::Module => {
metadata.push(Metadata::empty_module(0..input.len()))
}
wasmparser::Encoding::Component => {
metadata.push(Metadata::empty_component(0..input.len()))
}
}
}
}
ModuleSection {
unchecked_range: range,
..
} => metadata.push(Metadata::empty_module(range)),
ComponentSection {
unchecked_range: range,
..
} => metadata.push(Metadata::empty_component(range)),
End { .. } => {
let finished = metadata.pop().expect("non-empty metadata stack");
if metadata.is_empty() {
return Ok(finished);
} else {
metadata.last_mut().unwrap().push_child(finished);
}
}
CustomSection(c) => match c.as_known() {
KnownCustom::Name(_) => {
let names = ModuleNames::from_bytes(c.data(), c.data_offset())?;
if let Some(name) = names.get_name() {
metadata
.last_mut()
.expect("non-empty metadata stack")
.set_name(&name);
}
}
KnownCustom::ComponentName(_) => {
let names = ComponentNames::from_bytes(c.data(), c.data_offset())?;
if let Some(name) = names.get_name() {
metadata
.last_mut()
.expect("non-empty metadata stack")
.set_name(name);
}
}
KnownCustom::Producers(_) => {
let producers = Producers::from_bytes(c.data(), c.data_offset())?;
metadata
.last_mut()
.expect("non-empty metadata stack")
.set_producers(producers);
}
KnownCustom::Unknown if c.name() == "author" => {
let a = Author::parse_custom_section(&c)?;
match metadata.last_mut().expect("non-empty metadata stack") {
Metadata::Module { author, .. } => *author = Some(a),
Metadata::Component { author, .. } => *author = Some(a),
}
}
KnownCustom::Unknown if c.name() == "description" => {
let a = Description::parse_custom_section(&c)?;
match metadata.last_mut().expect("non-empty metadata stack") {
Metadata::Module { description, .. } => *description = Some(a),
Metadata::Component { description, .. } => *description = Some(a),
}
}
KnownCustom::Unknown if c.name() == "licenses" => {
let a = Licenses::parse_custom_section(&c)?;
match metadata.last_mut().expect("non-empty metadata stack") {
Metadata::Module { licenses, .. } => *licenses = Some(a),
Metadata::Component { licenses, .. } => *licenses = Some(a),
}
}
KnownCustom::Unknown if c.name() == "source" => {
let a = Source::parse_custom_section(&c)?;
match metadata.last_mut().expect("non-empty metadata stack") {
Metadata::Module { source, .. } => *source = Some(a),
Metadata::Component { source, .. } => *source = Some(a),
}
}
_ => {}
},
_ => {}
}
}
Err(anyhow::anyhow!(
"malformed wasm binary, should have reached end"
))
}

fn empty_component(range: Range<usize>) -> Self {
Metadata::Component {
name: None,
producers: None,
author: None,
description: None,
licenses: None,
source: None,
children: Vec::new(),
range,
}
}

fn empty_module(range: Range<usize>) -> Self {
Metadata::Module {
name: None,
producers: None,
author: None,
description: None,
licenses: None,
source: None,
range,
}
}
fn set_name(&mut self, n: &str) {
match self {
Metadata::Module { name, .. } => *name = Some(n.to_owned()),
Metadata::Component { name, .. } => *name = Some(n.to_owned()),
}
}
fn set_producers(&mut self, p: Producers) {
match self {
Metadata::Module { producers, .. } => *producers = Some(p),
Metadata::Component { producers, .. } => *producers = Some(p),
}
}
fn push_child(&mut self, child: Self) {
match self {
Metadata::Module { .. } => panic!("module shouldnt have children"),
Metadata::Component { children, .. } => children.push(Box::new(child)),
}
}

fn display(&self, f: &mut fmt::Formatter, indent: usize) -> fmt::Result {
let spaces = std::iter::repeat(" ").take(indent).collect::<String>();
match self {
Metadata::Module {
name, producers, ..
} => {
if let Some(name) = name {
writeln!(f, "{spaces}module {name}:")?;
} else {
writeln!(f, "{spaces}module:")?;
}
if let Some(producers) = producers {
producers.display(f, indent + 4)?;
}
Ok(())
}
Metadata::Component {
name,
producers,
children,
..
} => {
if let Some(name) = name {
writeln!(f, "{spaces}component {name}:")?;
} else {
writeln!(f, "{spaces}component:")?;
}
if let Some(producers) = producers {
producers.display(f, indent + 4)?;
}
for c in children {
c.display(f, indent + 4)?;
}
Ok(())
}
}
}
}

impl fmt::Display for Metadata {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
self.display(f, 0)
}
pub struct Metadata {
/// The component name, if any. Found in the component-name section.
pub name: Option<String>,
/// The component's producers section, if any.
pub producers: Option<Producers>,
/// The component's author section, if any.
pub author: Option<Author>,
/// Human-readable description of the binary
pub description: Option<Description>,
/// License(s) under which contained software is distributed as an SPDX License Expression.
pub licenses: Option<Licenses>,
/// URL to get source code for building the image
pub source: Option<Source>,
/// Byte range of the module in the parent binary
pub range: Range<usize>,
}
Loading
Loading