-
Notifications
You must be signed in to change notification settings - Fork 259
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[wasm-metadata] re-use metadata fields between modules and components (…
…#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
1 parent
9343c5d
commit f9f88b5
Showing
8 changed files
with
278 additions
and
273 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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>, | ||
} |
Oops, something went wrong.