Skip to content

Commit

Permalink
[wasm-metadata] re-use metadata fields between modules and components (
Browse files Browse the repository at this point in the history
…#1943)

* split metadata from payload

* [wasm-metadata] update tests to use Payload

* [wasm-metadata] fix display

* [wasm-metadata] use tuple enum for payload module
  • Loading branch information
yoshuawuyts authored Dec 9, 2024
1 parent 9343c5d commit f9f88b5
Show file tree
Hide file tree
Showing 8 changed files with 278 additions and 273 deletions.
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

0 comments on commit f9f88b5

Please sign in to comment.