chore: checkpoint before Python removal

This commit is contained in:
2026-03-26 22:33:59 +00:00
parent 683cec9307
commit e568ddf82a
29972 changed files with 11269302 additions and 2 deletions

View File

@@ -0,0 +1,91 @@
use crate::{Producers, rewrite_wasm};
use anyhow::Result;
use std::fmt::Debug;
/// Add metadata (module name, producers) to a WebAssembly file.
///
/// Supports both core WebAssembly modules and components. In components,
/// metadata will be added to the outermost component.
#[derive(Debug, Clone, Default)]
#[non_exhaustive]
pub struct AddMetadata {
/// Add a module or component name to the names section
pub name: AddMetadataField<String>,
/// Add a programming language to the producers section
pub language: Vec<(String, String)>,
/// Add a tool and its version to the producers section
pub processed_by: Vec<(String, String)>,
/// Add an SDK and its version to the producers section
pub sdk: Vec<(String, String)>,
/// Contact details of the people or organization responsible,
/// encoded as a freeform string.
#[cfg(feature = "oci")]
pub authors: AddMetadataField<crate::Authors>,
/// A human-readable description of the binary
#[cfg(feature = "oci")]
pub description: AddMetadataField<crate::Description>,
/// License(s) under which contained software is distributed as an SPDX License Expression.
#[cfg(feature = "oci")]
pub licenses: AddMetadataField<crate::Licenses>,
/// URL to get source code for building the image
#[cfg(feature = "oci")]
pub source: AddMetadataField<crate::Source>,
/// URL to find more information on the binary
#[cfg(feature = "oci")]
pub homepage: AddMetadataField<crate::Homepage>,
/// Source control revision identifier for the packaged software.
#[cfg(feature = "oci")]
pub revision: AddMetadataField<crate::Revision>,
/// Version of the packaged software
#[cfg(feature = "oci")]
pub version: AddMetadataField<crate::Version>,
}
impl AddMetadata {
/// Process a WebAssembly binary. Supports both core WebAssembly modules, and WebAssembly
/// components. The module and component will have, at very least, an empty name and producers
/// section created.
pub fn to_wasm(&self, input: &[u8]) -> Result<Vec<u8>> {
let add_producers = Producers::from_meta(self);
rewrite_wasm(self, &add_producers, input)
}
}
/// Defines how to modify a field of the component/module metadata
#[derive(Debug, Clone)]
pub enum AddMetadataField<T: Debug + Clone> {
/// Keep the existing value of the field
Keep,
/// Remove the existing value of the field
Clear,
/// Set the field to a new value
Set(T),
}
impl<T: Debug + Clone> AddMetadataField<T> {
/// Returns true if the field should be cleared
pub fn is_clear(&self) -> bool {
matches!(self, Self::Clear)
}
/// Returns true if the field should be kept
pub fn is_keep(&self) -> bool {
matches!(self, Self::Keep)
}
}
impl<T: Debug + Clone> Default for AddMetadataField<T> {
fn default() -> Self {
Self::Keep
}
}

169
vendor/wasm-metadata/src/clap.rs vendored Normal file
View File

@@ -0,0 +1,169 @@
use crate::AddMetadata;
use std::fmt::Debug;
/// Add metadata (module name, producers) to a WebAssembly file.
///
/// Supports both core WebAssembly modules and components. In components,
/// metadata will be added to the outermost component.
#[derive(clap::Parser, Debug, Clone, Default)]
#[non_exhaustive]
pub struct AddMetadataOpts {
/// Add a module or component name to the names section
#[clap(long, value_name = "NAME", conflicts_with = "clear_name")]
pub name: Option<String>,
/// Remove a module or component name from the names section
#[clap(long, conflicts_with = "name")]
pub clear_name: bool,
/// Add a programming language to the producers section
#[clap(long, value_parser = parse_key_value, value_name = "NAME=VERSION")]
pub language: Vec<(String, String)>,
/// Add a tool and its version to the producers section
#[clap(long = "processed-by", value_parser = parse_key_value, value_name="NAME=VERSION")]
pub processed_by: Vec<(String, String)>,
/// Add an SDK and its version to the producers section
#[clap(long, value_parser = parse_key_value, value_name="NAME=VERSION")]
pub sdk: Vec<(String, String)>,
/// Contact details of the people or organization responsible,
/// encoded as a freeform string.
#[clap(long, value_name = "NAME", conflicts_with = "clear_authors")]
#[cfg(feature = "oci")]
pub authors: Option<crate::Authors>,
/// Remove authors from the metadata
#[clap(long, conflicts_with = "authors")]
#[cfg(feature = "oci")]
pub clear_authors: bool,
/// A human-readable description of the binary
#[clap(long, value_name = "NAME", conflicts_with = "clear_description")]
#[cfg(feature = "oci")]
pub description: Option<crate::Description>,
/// Remove description from the metadata
#[clap(long, conflicts_with = "description")]
#[cfg(feature = "oci")]
pub clear_description: bool,
/// License(s) under which contained software is distributed as an SPDX License Expression.
#[clap(long, value_name = "NAME", conflicts_with = "clear_licenses")]
#[cfg(feature = "oci")]
pub licenses: Option<crate::Licenses>,
/// Remove licenses from the metadata
#[clap(long, conflicts_with = "licenses")]
#[cfg(feature = "oci")]
pub clear_licenses: bool,
/// URL to get source code for building the image
#[clap(long, value_name = "NAME", conflicts_with = "clear_source")]
#[cfg(feature = "oci")]
pub source: Option<crate::Source>,
/// Remove source from the metadata
#[clap(long, conflicts_with = "source")]
#[cfg(feature = "oci")]
pub clear_source: bool,
/// URL to find more information on the binary
#[clap(long, value_name = "NAME", conflicts_with = "clear_homepage")]
#[cfg(feature = "oci")]
pub homepage: Option<crate::Homepage>,
/// Remove homepage from the metadata
#[clap(long, conflicts_with = "homepage")]
#[cfg(feature = "oci")]
pub clear_homepage: bool,
/// Source control revision identifier for the packaged software.
#[clap(long, value_name = "NAME", conflicts_with = "clear_revision")]
#[cfg(feature = "oci")]
pub revision: Option<crate::Revision>,
/// Remove revision from the metadata
#[clap(long, conflicts_with = "revision")]
#[cfg(feature = "oci")]
pub clear_revision: bool,
/// Version of the packaged software
#[clap(long, value_name = "NAME", conflicts_with = "clear_version")]
#[cfg(feature = "oci")]
pub version: Option<crate::Version>,
/// Remove version from the metadata
#[clap(long, conflicts_with = "version")]
#[cfg(feature = "oci")]
pub clear_version: bool,
}
pub(crate) fn parse_key_value(s: &str) -> anyhow::Result<(String, String)> {
s.split_once('=')
.map(|(k, v)| (k.to_owned(), v.to_owned()))
.ok_or_else(|| anyhow::anyhow!("expected KEY=VALUE"))
}
impl From<AddMetadataOpts> for AddMetadata {
fn from(value: AddMetadataOpts) -> Self {
let mut add = AddMetadata::default();
if let Some(name) = value.name {
add.name = crate::AddMetadataField::Set(name);
} else if value.clear_name {
add.name = crate::AddMetadataField::Clear;
}
add.language = value.language;
add.processed_by = value.processed_by;
add.sdk = value.sdk;
#[cfg(feature = "oci")]
{
if let Some(authors) = value.authors {
add.authors = crate::AddMetadataField::Set(authors);
} else if value.clear_authors {
add.authors = crate::AddMetadataField::Clear;
}
if let Some(description) = value.description {
add.description = crate::AddMetadataField::Set(description);
} else if value.clear_description {
add.description = crate::AddMetadataField::Clear;
}
if let Some(licenses) = value.licenses {
add.licenses = crate::AddMetadataField::Set(licenses);
} else if value.clear_licenses {
add.licenses = crate::AddMetadataField::Clear;
}
if let Some(source) = value.source {
add.source = crate::AddMetadataField::Set(source);
} else if value.clear_source {
add.source = crate::AddMetadataField::Clear;
}
if let Some(homepage) = value.homepage {
add.homepage = crate::AddMetadataField::Set(homepage);
} else if value.clear_homepage {
add.homepage = crate::AddMetadataField::Clear;
}
if let Some(revision) = value.revision {
add.revision = crate::AddMetadataField::Set(revision);
} else if value.clear_revision {
add.revision = crate::AddMetadataField::Clear;
}
if let Some(version) = value.version {
add.version = crate::AddMetadataField::Set(version);
} else if value.clear_version {
add.version = crate::AddMetadataField::Clear;
}
}
add
}
}

142
vendor/wasm-metadata/src/dependencies.rs vendored Normal file
View File

@@ -0,0 +1,142 @@
use std::fmt::{self, Display};
use std::io::{Read, read_to_string};
use std::str::FromStr;
use anyhow::{Result, ensure};
use auditable_serde::VersionInfo;
use flate2::Compression;
use flate2::read::{ZlibDecoder, ZlibEncoder};
use serde::Serialize;
use wasm_encoder::{ComponentSection, CustomSection, Encode, Section};
use wasmparser::CustomSectionReader;
/// Human-readable description of the binary
#[derive(Debug, Clone, PartialEq)]
pub struct Dependencies {
version_info: VersionInfo,
custom_section: CustomSection<'static>,
}
impl Dependencies {
/// Parse an `description` custom section from a wasm binary.
pub(crate) fn parse_custom_section(reader: &CustomSectionReader<'_>) -> Result<Self> {
ensure!(
reader.name() == ".dep-v0",
"The `dependencies` custom section should have a name of '.dep-v0'"
);
let decompressed_data = read_to_string(ZlibDecoder::new(reader.data()))?;
let dependency_tree = auditable_serde::VersionInfo::from_str(&decompressed_data)?;
Ok(Self {
version_info: dependency_tree,
custom_section: CustomSection {
name: ".dep-v0".into(),
data: reader.data().to_owned().into(),
},
})
}
/// Create a new instance of `Dependencies`.
pub fn new(dependency_tree: auditable_serde::VersionInfo) -> Self {
let data = serde_json::to_string(&dependency_tree).unwrap();
let mut ret_vec = Vec::new();
let mut encoder = ZlibEncoder::new(data.as_bytes(), Compression::fast());
encoder.read_to_end(&mut ret_vec).unwrap();
Self {
version_info: dependency_tree,
custom_section: CustomSection {
name: ".dep-v0".into(),
data: ret_vec.into(),
},
}
}
/// Provides access to the version information stored in the object
pub fn version_info(&self) -> &VersionInfo {
&self.version_info
}
}
impl Serialize for Dependencies {
fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
serializer.serialize_str(&self.to_string())
}
}
impl Display for Dependencies {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
// NOTE: this will never panic since we always guarantee the data is
// encoded as utf8, even if we internally store it as [u8].
// let data = String::from_utf8(self.0.data.to_vec()).unwrap();
let data = serde_json::to_string(&self.version_info).unwrap();
write!(f, "{data}")
}
}
impl ComponentSection for Dependencies {
fn id(&self) -> u8 {
ComponentSection::id(&self.custom_section)
}
}
impl Section for Dependencies {
fn id(&self) -> u8 {
Section::id(&self.custom_section)
}
}
impl Encode for Dependencies {
fn encode(&self, sink: &mut Vec<u8>) {
self.custom_section.encode(sink);
}
}
#[cfg(test)]
mod test {
use super::*;
use auditable_serde::{Source, VersionInfo};
use std::str::FromStr;
use wasm_encoder::Component;
use wasmparser::Payload;
#[test]
fn roundtrip() {
let json_str = r#"{"packages":[{"name":"adler","version":"0.2.3","source":"registry"}]}"#;
let info = VersionInfo::from_str(json_str).unwrap();
assert_eq!(&info.packages[0].name, "adler");
let mut component = Component::new();
component.section(&Dependencies::new(info));
let component = component.finish();
let mut parsed = false;
for section in wasmparser::Parser::new(0).parse_all(&component) {
if let Payload::CustomSection(reader) = section.unwrap() {
let dependencies = Dependencies::parse_custom_section(&reader).unwrap();
assert_eq!(dependencies.to_string(), json_str);
parsed = true;
}
}
assert!(parsed);
}
#[test]
fn serialize() {
let json_str = r#"{"packages":[{"name":"adler","version":"0.2.3","source":"registry"}]}"#;
let info = VersionInfo::from_str(json_str).unwrap();
let dependencies = Dependencies::new(info);
assert_eq!(dependencies.version_info().packages[0].name, "adler");
assert_eq!(
dependencies.version_info().packages[0].version.to_string(),
"0.2.3"
);
assert_eq!(
dependencies.version_info().packages[0].source,
Source::Registry,
);
}
}

83
vendor/wasm-metadata/src/lib.rs vendored Normal file
View File

@@ -0,0 +1,83 @@
//! Read and manipulate WebAssembly metadata
//!
//! # Examples
//!
//! **Read metadata from a Wasm binary**
//!
//! ```no_run
//! # #![allow(unused)]
//! # fn main() -> Result<(), anyhow::Error> {
//! use wasm_metadata::Payload;
//! use std::fs;
//!
//! let wasm = fs::read("program.wasm")?;
//! let metadata = Payload::from_binary(&wasm)?.metadata();
//! # Ok(()) }
//! ```
//!
//! **Add metadata to a Wasm binary**
//!
//! ```no_run
//! # #![allow(unused)]
//! # fn main() -> Result<(), anyhow::Error> {
//! use wasm_metadata::*;
//! use std::fs;
//!
//! let wasm = fs::read("program.wasm")?;
//!
//! let mut add = AddMetadata ::default();
//! add.name = AddMetadataField::Set("program".to_owned());
//! add.language = vec![("tunalang".to_owned(), "1.0.0".to_owned())];
//! add.processed_by = vec![("chashu-tools".to_owned(), "1.0.1".to_owned())];
//! add.sdk = vec![];
//! add.authors = AddMetadataField::Set(Authors::new("Chashu Cat"));
//! add.description = AddMetadataField::Set(Description::new("Chashu likes tuna"));
//! add.licenses = AddMetadataField::Set(Licenses::new("Apache-2.0 WITH LLVM-exception")?);
//! add.source = AddMetadataField::Set(Source::new("https://github.com/chashu/chashu-tools")?);
//! add.homepage = AddMetadataField::Set(Homepage::new("https://github.com/chashu/chashu-tools")?);
//! add.revision = AddMetadataField::Set(Revision::new("de978e17a80c1118f606fce919ba9b7d5a04a5ad"));
//! add.version = AddMetadataField::Set(Version::new("1.0.0"));
//!
//! let wasm = add.to_wasm(&wasm)?;
//! fs::write("program.wasm", &wasm)?;
//! # Ok(()) }
//! ```
#![cfg_attr(docsrs, feature(doc_cfg))]
#![warn(missing_debug_implementations, missing_docs)]
pub use add_metadata::{AddMetadata, AddMetadataField};
pub use names::{ComponentNames, ModuleNames};
pub use producers::{Producers, ProducersField};
pub(crate) use rewrite::rewrite_wasm;
mod add_metadata;
#[cfg(feature = "clap")]
mod clap;
mod names;
mod producers;
mod rewrite;
pub(crate) mod utils;
#[cfg(feature = "oci")]
mod dependencies;
#[cfg(feature = "oci")]
pub use dependencies::Dependencies;
#[cfg(feature = "oci")]
mod oci_annotations;
#[cfg(feature = "oci")]
pub use oci_annotations::{Authors, Description, Homepage, Licenses, Revision, Source, Version};
#[cfg(feature = "oci")]
mod metadata;
#[cfg(feature = "oci")]
pub use metadata::Metadata;
#[cfg(feature = "oci")]
mod payload;
#[cfg(feature = "oci")]
pub use payload::Payload;
#[cfg(feature = "clap")]
pub use clap::AddMetadataOpts;

34
vendor/wasm-metadata/src/metadata.rs vendored Normal file
View File

@@ -0,0 +1,34 @@
use serde_derive::Serialize;
use std::ops::Range;
use crate::{
Authors, Dependencies, Description, Homepage, Licenses, Producers, Revision, Source, Version,
};
/// Metadata associated with a Wasm Component or Module
#[derive(Debug, Serialize, Default)]
#[serde(rename_all = "lowercase")]
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 authors section, if any.
pub authors: Option<Authors>,
/// 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>,
/// URL to find more information on the binary
pub homepage: Option<Homepage>,
/// Source control revision identifier for the packaged software.
pub revision: Option<Revision>,
/// Version of the packaged software
pub version: Option<Version>,
/// Byte range of the module in the parent binary
pub range: Range<usize>,
/// Dependencies of the component
pub dependencies: Option<Dependencies>,
}

View File

@@ -0,0 +1,107 @@
use std::fmt::{self, Debug};
use anyhow::Result;
use wasm_encoder::Encode;
use wasmparser::{BinaryReader, ComponentNameSectionReader};
use crate::utils::name_map;
/// Helper for rewriting a component's component-name section with a new component name.
pub struct ComponentNames<'a> {
component_name: Option<String>,
names: Vec<wasmparser::ComponentName<'a>>,
}
impl<'a> Debug for ComponentNames<'a> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("ComponentNames")
.field("component_name", &self.component_name)
.finish_non_exhaustive()
}
}
impl<'a> ComponentNames<'a> {
/// Create an empty component-name section.
pub fn empty() -> Self {
ComponentNames {
component_name: None,
names: Vec::new(),
}
}
/// Read a component-name section from a WebAssembly binary. Records the component name, as
/// well as all other component name fields for later serialization.
pub fn from_bytes(bytes: &'a [u8], offset: usize) -> Result<ComponentNames<'a>> {
let reader = BinaryReader::new(bytes, offset);
let section = ComponentNameSectionReader::new(reader);
let mut s = Self::empty();
for name in section.into_iter() {
let name = name?;
match name {
wasmparser::ComponentName::Component { name, .. } => {
s.component_name = Some(name.to_owned())
}
_ => s.names.push(name),
}
}
Ok(s)
}
/// Set component name according to [`AddMetadata`]
pub(crate) fn from_name(name: &Option<String>) -> Self {
let mut s = Self::empty();
s.component_name = name.clone();
s
}
/// Merge with another section
pub(crate) fn merge(&mut self, other: &Self) {
if other.component_name.is_some() {
self.component_name = other.component_name.clone();
}
self.names.extend_from_slice(&other.names);
}
/// Set component name
pub fn set_name(&mut self, name: &str) {
self.component_name = Some(name.to_owned())
}
/// Get component name
pub fn get_name(&self) -> Option<&String> {
self.component_name.as_ref()
}
/// Serialize into [`wasm_encoder::ComponentNameSection`]
pub(crate) fn section(&self) -> Result<wasm_encoder::ComponentNameSection> {
let mut section = wasm_encoder::ComponentNameSection::new();
if let Some(component_name) = &self.component_name {
section.component(&component_name);
}
for n in self.names.iter() {
match n {
wasmparser::ComponentName::Component { .. } => unreachable!(),
wasmparser::ComponentName::CoreFuncs(m) => section.core_funcs(&name_map(&m)?),
wasmparser::ComponentName::CoreGlobals(m) => section.core_globals(&name_map(&m)?),
wasmparser::ComponentName::CoreMemories(m) => section.core_memories(&name_map(&m)?),
wasmparser::ComponentName::CoreTables(m) => section.core_tables(&name_map(&m)?),
wasmparser::ComponentName::CoreTags(m) => section.core_tags(&name_map(&m)?),
wasmparser::ComponentName::CoreModules(m) => section.core_modules(&name_map(&m)?),
wasmparser::ComponentName::CoreInstances(m) => {
section.core_instances(&name_map(&m)?)
}
wasmparser::ComponentName::CoreTypes(m) => section.core_types(&name_map(&m)?),
wasmparser::ComponentName::Types(m) => section.types(&name_map(&m)?),
wasmparser::ComponentName::Instances(m) => section.instances(&name_map(&m)?),
wasmparser::ComponentName::Components(m) => section.components(&name_map(&m)?),
wasmparser::ComponentName::Funcs(m) => section.funcs(&name_map(&m)?),
wasmparser::ComponentName::Values(m) => section.values(&name_map(&m)?),
wasmparser::ComponentName::Unknown { .. } => {} // wasm-encoder doesn't support it
}
}
Ok(section)
}
/// Serialize into the raw bytes of a wasm custom section.
pub fn raw_custom_section(&self) -> Result<Vec<u8>> {
let mut ret = Vec::new();
self.section()?.encode(&mut ret);
Ok(ret)
}
}

5
vendor/wasm-metadata/src/names/mod.rs vendored Normal file
View File

@@ -0,0 +1,5 @@
mod component;
mod module;
pub use component::ComponentNames;
pub use module::ModuleNames;

101
vendor/wasm-metadata/src/names/module.rs vendored Normal file
View File

@@ -0,0 +1,101 @@
use std::fmt::{self, Debug};
use anyhow::Result;
use wasm_encoder::Encode;
use wasmparser::{BinaryReader, NameSectionReader};
use crate::utils::{indirect_name_map, name_map};
/// Helper for rewriting a module's name section with a new module name.
pub struct ModuleNames<'a> {
module_name: Option<String>,
names: Vec<wasmparser::Name<'a>>,
}
impl<'a> Debug for ModuleNames<'a> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("ModuleNames")
.field("module_name", &self.module_name)
.finish_non_exhaustive()
}
}
impl<'a> ModuleNames<'a> {
/// Create an empty name section.
pub fn empty() -> Self {
ModuleNames {
module_name: None,
names: Vec::new(),
}
}
/// Read a name section from a WebAssembly binary. Records the module name, and all other
/// contents of name section, for later serialization.
pub fn from_bytes(bytes: &'a [u8], offset: usize) -> Result<ModuleNames<'a>> {
let reader = BinaryReader::new(bytes, offset);
let section = NameSectionReader::new(reader);
let mut s = Self::empty();
for name in section.into_iter() {
let name = name?;
match name {
wasmparser::Name::Module { name, .. } => s.module_name = Some(name.to_owned()),
_ => s.names.push(name),
}
}
Ok(s)
}
/// Update module section according to [`AddMetadata`]
pub(crate) fn from_name(name: &Option<String>) -> Self {
let mut s = Self::empty();
s.module_name = name.clone();
s
}
/// Merge with another section
pub(crate) fn merge(&mut self, other: &Self) {
if other.module_name.is_some() {
self.module_name = other.module_name.clone();
}
self.names.extend_from_slice(&other.names);
}
/// Set module name
pub fn set_name(&mut self, name: &str) {
self.module_name = Some(name.to_owned())
}
/// Get module name
pub fn get_name(&self) -> Option<&String> {
self.module_name.as_ref()
}
/// Serialize into [`wasm_encoder::NameSection`].
pub(crate) fn section(&self) -> Result<wasm_encoder::NameSection> {
let mut section = wasm_encoder::NameSection::new();
if let Some(module_name) = &self.module_name {
section.module(&module_name);
}
for n in self.names.iter() {
match n {
wasmparser::Name::Module { .. } => unreachable!(),
wasmparser::Name::Function(m) => section.functions(&name_map(&m)?),
wasmparser::Name::Local(m) => section.locals(&indirect_name_map(&m)?),
wasmparser::Name::Label(m) => section.labels(&indirect_name_map(&m)?),
wasmparser::Name::Type(m) => section.types(&name_map(&m)?),
wasmparser::Name::Table(m) => section.tables(&name_map(&m)?),
wasmparser::Name::Memory(m) => section.memories(&name_map(&m)?),
wasmparser::Name::Global(m) => section.globals(&name_map(&m)?),
wasmparser::Name::Element(m) => section.elements(&name_map(&m)?),
wasmparser::Name::Data(m) => section.data(&name_map(&m)?),
wasmparser::Name::Field(m) => section.fields(&indirect_name_map(&m)?),
wasmparser::Name::Tag(m) => section.tags(&name_map(&m)?),
wasmparser::Name::Unknown { .. } => {} // wasm-encoder doesn't support it
}
}
Ok(section)
}
/// Serialize into the raw bytes of a wasm custom section.
pub fn raw_custom_section(&self) -> Result<Vec<u8>> {
let mut ret = Vec::new();
self.section()?.encode(&mut ret);
Ok(ret)
}
}

View File

@@ -0,0 +1,111 @@
use std::borrow::Cow;
use std::fmt::{self, Display};
use std::str::FromStr;
use anyhow::{Error, Result, ensure};
use serde::Serialize;
use wasm_encoder::{ComponentSection, CustomSection, Encode, Section};
use wasmparser::CustomSectionReader;
/// Contact details of the people or organization responsible,
/// encoded as a freeform string.
#[derive(Debug, Clone, PartialEq)]
pub struct Authors(CustomSection<'static>);
impl Authors {
/// Create a new instance of `Authors`.
pub fn new<S: Into<Cow<'static, str>>>(s: S) -> Self {
Self(CustomSection {
name: "authors".into(),
data: match s.into() {
Cow::Borrowed(s) => Cow::Borrowed(s.as_bytes()),
Cow::Owned(s) => Cow::Owned(s.into()),
},
})
}
/// Parse an `authors` custom section from a wasm binary.
pub(crate) fn parse_custom_section(reader: &CustomSectionReader<'_>) -> Result<Self> {
ensure!(
reader.name() == "authors",
"The `authors` custom section should have a name of 'authors'"
);
let data = String::from_utf8(reader.data().to_owned())?;
Ok(Self::new(data))
}
}
impl FromStr for Authors {
type Err = Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
Ok(Self::new(s.to_owned()))
}
}
impl Serialize for Authors {
fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
serializer.serialize_str(&self.to_string())
}
}
impl Display for Authors {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
// NOTE: this will never panic since we always guarantee the data is
// encoded as utf8, even if we internally store it as [u8].
let data = String::from_utf8(self.0.data.to_vec()).unwrap();
write!(f, "{data}")
}
}
impl ComponentSection for Authors {
fn id(&self) -> u8 {
ComponentSection::id(&self.0)
}
}
impl Section for Authors {
fn id(&self) -> u8 {
Section::id(&self.0)
}
}
impl Encode for Authors {
fn encode(&self, sink: &mut Vec<u8>) {
self.0.encode(sink);
}
}
#[cfg(test)]
mod test {
use super::*;
use wasm_encoder::Component;
use wasmparser::Payload;
#[test]
fn roundtrip() {
let mut component = Component::new();
component.section(&Authors::new("Nori Cat"));
let component = component.finish();
let mut parsed = false;
for section in wasmparser::Parser::new(0).parse_all(&component) {
if let Payload::CustomSection(reader) = section.unwrap() {
let authors = Authors::parse_custom_section(&reader).unwrap();
assert_eq!(authors.to_string(), "Nori Cat");
parsed = true;
}
}
assert!(parsed);
}
#[test]
fn serialize() {
let authors = Authors::new("Chashu Cat");
let json = serde_json::to_string(&authors).unwrap();
assert_eq!(r#""Chashu Cat""#, json);
}
}

View File

@@ -0,0 +1,110 @@
use std::borrow::Cow;
use std::fmt::{self, Display};
use std::str::FromStr;
use anyhow::{Error, Result, ensure};
use serde::Serialize;
use wasm_encoder::{ComponentSection, CustomSection, Encode, Section};
use wasmparser::CustomSectionReader;
/// Human-readable description of the binary
#[derive(Debug, Clone, PartialEq)]
pub struct Description(CustomSection<'static>);
impl Description {
/// Create a new instance of `Desrciption`.
pub fn new<S: Into<Cow<'static, str>>>(s: S) -> Self {
Self(CustomSection {
name: "description".into(),
data: match s.into() {
Cow::Borrowed(s) => Cow::Borrowed(s.as_bytes()),
Cow::Owned(s) => Cow::Owned(s.into()),
},
})
}
/// Parse an `description` custom section from a wasm binary.
pub(crate) fn parse_custom_section(reader: &CustomSectionReader<'_>) -> Result<Self> {
ensure!(
reader.name() == "description",
"The `description` custom section should have a name of 'description'"
);
let data = String::from_utf8(reader.data().to_owned())?;
Ok(Self::new(data))
}
}
impl FromStr for Description {
type Err = Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
Ok(Self::new(s.to_owned()))
}
}
impl Serialize for Description {
fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
serializer.serialize_str(&self.to_string())
}
}
impl Display for Description {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
// NOTE: this will never panic since we always guarantee the data is
// encoded as utf8, even if we internally store it as [u8].
let data = String::from_utf8(self.0.data.to_vec()).unwrap();
write!(f, "{data}")
}
}
impl ComponentSection for Description {
fn id(&self) -> u8 {
ComponentSection::id(&self.0)
}
}
impl Section for Description {
fn id(&self) -> u8 {
Section::id(&self.0)
}
}
impl Encode for Description {
fn encode(&self, sink: &mut Vec<u8>) {
self.0.encode(sink);
}
}
#[cfg(test)]
mod test {
use super::*;
use wasm_encoder::Component;
use wasmparser::Payload;
#[test]
fn roundtrip() {
let mut component = Component::new();
component.section(&Description::new("Nori likes chicken"));
let component = component.finish();
let mut parsed = false;
for section in wasmparser::Parser::new(0).parse_all(&component) {
if let Payload::CustomSection(reader) = section.unwrap() {
let description = Description::parse_custom_section(&reader).unwrap();
assert_eq!(description.to_string(), "Nori likes chicken");
parsed = true;
}
}
assert!(parsed);
}
#[test]
fn serialize() {
let description = Description::new("Chashu likes tuna");
let json = serde_json::to_string(&description).unwrap();
assert_eq!(r#""Chashu likes tuna""#, json);
}
}

View File

@@ -0,0 +1,118 @@
use std::borrow::Cow;
use std::fmt::{self, Display};
use std::str::FromStr;
use anyhow::{Error, Result, ensure};
use serde::Serialize;
use url::Url;
use wasm_encoder::{ComponentSection, CustomSection, Encode, Section};
use wasmparser::CustomSectionReader;
/// URL to find more information on the binary
#[derive(Debug, Clone, PartialEq)]
pub struct Homepage(CustomSection<'static>);
impl Homepage {
/// Create a new instance of `Homepage`.
pub fn new(s: &str) -> Result<Self> {
Ok(Url::parse(s)?.into())
}
/// Parse a `homepage` custom section from a wasm binary.
pub(crate) fn parse_custom_section(reader: &CustomSectionReader<'_>) -> Result<Self> {
ensure!(
reader.name() == "homepage",
"The `homepage` custom section should have a name of 'homepage'"
);
let data = String::from_utf8(reader.data().to_owned())?;
Self::new(&data)
}
}
impl FromStr for Homepage {
type Err = Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
Self::new(s)
}
}
impl From<Url> for Homepage {
fn from(expression: Url) -> Self {
Self(CustomSection {
name: "homepage".into(),
data: Cow::Owned(expression.to_string().into_bytes()),
})
}
}
impl Serialize for Homepage {
fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
serializer.serialize_str(&self.to_string())
}
}
impl Display for Homepage {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
// NOTE: this will never panic since we always guarantee the data is
// encoded as utf8, even if we internally store it as [u8].
let data = String::from_utf8(self.0.data.to_vec()).unwrap();
write!(f, "{data}")
}
}
impl ComponentSection for Homepage {
fn id(&self) -> u8 {
ComponentSection::id(&self.0)
}
}
impl Section for Homepage {
fn id(&self) -> u8 {
Section::id(&self.0)
}
}
impl Encode for Homepage {
fn encode(&self, sink: &mut Vec<u8>) {
self.0.encode(sink);
}
}
#[cfg(test)]
mod test {
use super::*;
use wasm_encoder::Component;
use wasmparser::Payload;
#[test]
fn roundtrip() {
let mut component = Component::new();
component
.section(&Homepage::new("https://github.com/bytecodealliance/wasm-tools").unwrap());
let component = component.finish();
let mut parsed = false;
for section in wasmparser::Parser::new(0).parse_all(&component) {
if let Payload::CustomSection(reader) = section.unwrap() {
let description = Homepage::parse_custom_section(&reader).unwrap();
assert_eq!(
description.to_string(),
"https://github.com/bytecodealliance/wasm-tools"
);
parsed = true;
}
}
assert!(parsed);
}
#[test]
fn serialize() {
let description = Homepage::new("https://github.com/bytecodealliance/wasm-tools").unwrap();
let json = serde_json::to_string(&description).unwrap();
assert_eq!(r#""https://github.com/bytecodealliance/wasm-tools""#, json);
}
}

View File

@@ -0,0 +1,122 @@
use std::borrow::Cow;
use std::fmt::{self, Display};
use std::str::FromStr;
use anyhow::{Error, Result, ensure};
use serde::Serialize;
use wasm_encoder::{ComponentSection, CustomSection, Encode, Section};
use wasmparser::CustomSectionReader;
/// License(s) under which contained software is distributed as an SPDX License Expression.
#[derive(Debug, Clone, PartialEq)]
pub struct Licenses(CustomSection<'static>);
impl Licenses {
/// Create a new instance of `Licenses`.
pub fn new(s: &str) -> Result<Self> {
Ok(spdx::Expression::parse(s)?.into())
}
/// Parse a `licenses` custom section from a wasm binary.
pub(crate) fn parse_custom_section(reader: &CustomSectionReader<'_>) -> Result<Self> {
ensure!(
reader.name() == "licenses",
"The `licenses` custom section should have a name of 'license'"
);
let data = String::from_utf8(reader.data().to_owned())?;
Self::new(&data)
}
}
impl FromStr for Licenses {
type Err = Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
Self::new(s)
}
}
impl From<spdx::Expression> for Licenses {
fn from(expression: spdx::Expression) -> Self {
Self(CustomSection {
name: "licenses".into(),
data: Cow::Owned(expression.to_string().into_bytes()),
})
}
}
impl Serialize for Licenses {
fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
serializer.serialize_str(&self.to_string())
}
}
impl Display for Licenses {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
// NOTE: this will never panic since we always guarantee the data is
// encoded as utf8, even if we internally store it as [u8].
let data = String::from_utf8(self.0.data.to_vec()).unwrap();
write!(f, "{data}")
}
}
impl ComponentSection for Licenses {
fn id(&self) -> u8 {
ComponentSection::id(&self.0)
}
}
impl Section for Licenses {
fn id(&self) -> u8 {
Section::id(&self.0)
}
}
impl Encode for Licenses {
fn encode(&self, sink: &mut Vec<u8>) {
self.0.encode(sink);
}
}
#[cfg(test)]
mod test {
use super::*;
use wasm_encoder::Component;
use wasmparser::Payload;
#[test]
fn roundtrip() {
let mut component = Component::new();
component.section(
&Licenses::new("Apache-2.0 WITH LLVM-exception OR Apache-2.0 OR MIT").unwrap(),
);
let component = component.finish();
let mut parsed = false;
for section in wasmparser::Parser::new(0).parse_all(&component) {
if let Payload::CustomSection(reader) = section.unwrap() {
let description = Licenses::parse_custom_section(&reader).unwrap();
assert_eq!(
description.to_string(),
"Apache-2.0 WITH LLVM-exception OR Apache-2.0 OR MIT"
);
parsed = true;
}
}
assert!(parsed);
}
#[test]
fn serialize() {
let description =
Licenses::new("Apache-2.0 WITH LLVM-exception OR Apache-2.0 OR MIT").unwrap();
let json = serde_json::to_string(&description).unwrap();
assert_eq!(
r#""Apache-2.0 WITH LLVM-exception OR Apache-2.0 OR MIT""#,
json
);
}
}

View File

@@ -0,0 +1,32 @@
//! Annotations following the [OCI Annotations Spec].
//!
//! The fields of these annotations are encoded into custom sections of
//! component binaries, and are explicitly compatible with the OCI Annotations
//! Spec. That enables Compontents to be encoded to OCI and back without needing
//! to perform any additional parsing. This greatly simplifies adding metadata to
//! component registries, since language-native component toolchains can encode them
//! directly into components. Which in turn can be picked up by Component-to-OCI
//! tooling to take those annotations and display them in a way that registries can
//! understand.
//!
//! For the files in this submodule that means we want to be explicitly
//! compatible with the OCI Annotations specification. Any deviation in our
//! parsing rules from the spec should be considered a bug we have to fix.
//!
//! [OCI Annotations Spec]: https://specs.opencontainers.org/image-spec/annotations/
pub use authors::Authors;
pub use description::Description;
pub use homepage::Homepage;
pub use licenses::Licenses;
pub use revision::Revision;
pub use source::Source;
pub use version::Version;
mod authors;
mod description;
mod homepage;
mod licenses;
mod revision;
mod source;
mod version;

View File

@@ -0,0 +1,113 @@
use std::borrow::Cow;
use std::fmt::{self, Display};
use std::str::FromStr;
use anyhow::{Error, Result, ensure};
use serde::Serialize;
use wasm_encoder::{ComponentSection, CustomSection, Encode, Section};
use wasmparser::CustomSectionReader;
/// Source control revision identifier for the packaged software.
#[derive(Debug, Clone, PartialEq)]
pub struct Revision(CustomSection<'static>);
impl Revision {
/// Create a new instance of `Desrciption`.
pub fn new<S: Into<Cow<'static, str>>>(s: S) -> Self {
Self(CustomSection {
name: "revision".into(),
data: match s.into() {
Cow::Borrowed(s) => Cow::Borrowed(s.as_bytes()),
Cow::Owned(s) => Cow::Owned(s.into()),
},
})
}
/// Parse an `revision` custom section from a wasm binary.
pub(crate) fn parse_custom_section(reader: &CustomSectionReader<'_>) -> Result<Self> {
ensure!(
reader.name() == "revision",
"The `revision` custom section should have a name of 'revision'"
);
let data = String::from_utf8(reader.data().to_owned())?;
Ok(Self::new(data))
}
}
impl FromStr for Revision {
type Err = Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
Ok(Self::new(s.to_owned()))
}
}
impl Serialize for Revision {
fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
serializer.serialize_str(&self.to_string())
}
}
impl Display for Revision {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
// NOTE: this will never panic since we always guarantee the data is
// encoded as utf8, even if we internally store it as [u8].
let data = String::from_utf8(self.0.data.to_vec()).unwrap();
write!(f, "{data}")
}
}
impl ComponentSection for Revision {
fn id(&self) -> u8 {
ComponentSection::id(&self.0)
}
}
impl Section for Revision {
fn id(&self) -> u8 {
Section::id(&self.0)
}
}
impl Encode for Revision {
fn encode(&self, sink: &mut Vec<u8>) {
self.0.encode(sink);
}
}
#[cfg(test)]
mod test {
use super::*;
use wasm_encoder::Component;
use wasmparser::Payload;
#[test]
fn roundtrip() {
let mut component = Component::new();
component.section(&Revision::new("de978e17a80c1118f606fce919ba9b7d5a04a5ad"));
let component = component.finish();
let mut parsed = false;
for section in wasmparser::Parser::new(0).parse_all(&component) {
if let Payload::CustomSection(reader) = section.unwrap() {
let revision = Revision::parse_custom_section(&reader).unwrap();
assert_eq!(
revision.to_string(),
"de978e17a80c1118f606fce919ba9b7d5a04a5ad"
);
parsed = true;
}
}
assert!(parsed);
}
#[test]
fn serialize() {
let revision = Revision::new("de978e17a80c1118f606fce919ba9b7d5a04a5ad");
let json = serde_json::to_string(&revision).unwrap();
assert_eq!(r#""de978e17a80c1118f606fce919ba9b7d5a04a5ad""#, json);
}
}

View File

@@ -0,0 +1,117 @@
use std::borrow::Cow;
use std::fmt::{self, Display};
use std::str::FromStr;
use anyhow::{Error, Result, ensure};
use serde::Serialize;
use url::Url;
use wasm_encoder::{ComponentSection, CustomSection, Encode, Section};
use wasmparser::CustomSectionReader;
/// URL to get source code for building the image
#[derive(Debug, Clone, PartialEq)]
pub struct Source(CustomSection<'static>);
impl Source {
/// Create a new instance of `Source`.
pub fn new(s: &str) -> Result<Self> {
Ok(Url::parse(s)?.into())
}
/// Parse a `source` custom section from a wasm binary.
pub(crate) fn parse_custom_section(reader: &CustomSectionReader<'_>) -> Result<Self> {
ensure!(
reader.name() == "source",
"The `source` custom section should have a name of 'source'"
);
let data = String::from_utf8(reader.data().to_owned())?;
Self::new(&data)
}
}
impl FromStr for Source {
type Err = Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
Self::new(s)
}
}
impl From<Url> for Source {
fn from(expression: Url) -> Self {
Self(CustomSection {
name: "source".into(),
data: Cow::Owned(expression.to_string().into_bytes()),
})
}
}
impl Serialize for Source {
fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
serializer.serialize_str(&self.to_string())
}
}
impl Display for Source {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
// NOTE: this will never panic since we always guarantee the data is
// encoded as utf8, even if we internally store it as [u8].
let data = String::from_utf8(self.0.data.to_vec()).unwrap();
write!(f, "{data}")
}
}
impl ComponentSection for Source {
fn id(&self) -> u8 {
ComponentSection::id(&self.0)
}
}
impl Section for Source {
fn id(&self) -> u8 {
Section::id(&self.0)
}
}
impl Encode for Source {
fn encode(&self, sink: &mut Vec<u8>) {
self.0.encode(sink);
}
}
#[cfg(test)]
mod test {
use super::*;
use wasm_encoder::Component;
use wasmparser::Payload;
#[test]
fn roundtrip() {
let mut component = Component::new();
component.section(&Source::new("https://github.com/bytecodealliance/wasm-tools").unwrap());
let component = component.finish();
let mut parsed = false;
for section in wasmparser::Parser::new(0).parse_all(&component) {
if let Payload::CustomSection(reader) = section.unwrap() {
let description = Source::parse_custom_section(&reader).unwrap();
assert_eq!(
description.to_string(),
"https://github.com/bytecodealliance/wasm-tools"
);
parsed = true;
}
}
assert!(parsed);
}
#[test]
fn serialize() {
let description = Source::new("https://github.com/bytecodealliance/wasm-tools").unwrap();
let json = serde_json::to_string(&description).unwrap();
assert_eq!(r#""https://github.com/bytecodealliance/wasm-tools""#, json);
}
}

View File

@@ -0,0 +1,110 @@
use std::borrow::Cow;
use std::fmt::{self, Display};
use std::str::FromStr;
use anyhow::{Error, Result, ensure};
use serde::Serialize;
use wasm_encoder::{ComponentSection, CustomSection, Encode, Section};
use wasmparser::CustomSectionReader;
/// Version of the packaged software
#[derive(Debug, Clone, PartialEq)]
pub struct Version(CustomSection<'static>);
impl Version {
/// Create a new instance of `Desrciption`.
pub fn new<S: Into<Cow<'static, str>>>(s: S) -> Self {
Self(CustomSection {
name: "version".into(),
data: match s.into() {
Cow::Borrowed(s) => Cow::Borrowed(s.as_bytes()),
Cow::Owned(s) => Cow::Owned(s.into()),
},
})
}
/// Parse an `version` custom section from a wasm binary.
pub(crate) fn parse_custom_section(reader: &CustomSectionReader<'_>) -> Result<Self> {
ensure!(
reader.name() == "version",
"The `version` custom section should have a name of 'version'"
);
let data = String::from_utf8(reader.data().to_owned())?;
Ok(Self::new(data))
}
}
impl FromStr for Version {
type Err = Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
Ok(Self::new(s.to_owned()))
}
}
impl Serialize for Version {
fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
serializer.serialize_str(&self.to_string())
}
}
impl Display for Version {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
// NOTE: this will never panic since we always guarantee the data is
// encoded as utf8, even if we internally store it as [u8].
let data = String::from_utf8(self.0.data.to_vec()).unwrap();
write!(f, "{data}")
}
}
impl ComponentSection for Version {
fn id(&self) -> u8 {
ComponentSection::id(&self.0)
}
}
impl Section for Version {
fn id(&self) -> u8 {
Section::id(&self.0)
}
}
impl Encode for Version {
fn encode(&self, sink: &mut Vec<u8>) {
self.0.encode(sink);
}
}
#[cfg(test)]
mod test {
use super::*;
use wasm_encoder::Component;
use wasmparser::Payload;
#[test]
fn roundtrip() {
let mut component = Component::new();
component.section(&Version::new("1.0.0"));
let component = component.finish();
let mut parsed = false;
for section in wasmparser::Parser::new(0).parse_all(&component) {
if let Payload::CustomSection(reader) = section.unwrap() {
let version = Version::parse_custom_section(&reader).unwrap();
assert_eq!(version.to_string(), "1.0.0");
parsed = true;
}
}
assert!(parsed);
}
#[test]
fn serialize() {
let version = Version::new("1.0.0");
let json = serde_json::to_string(&version).unwrap();
assert_eq!(r#""1.0.0""#, json);
}
}

215
vendor/wasm-metadata/src/payload.rs vendored Normal file
View File

@@ -0,0 +1,215 @@
use std::ops::Range;
use anyhow::Result;
use serde_derive::Serialize;
use wasmparser::{KnownCustom, Parser, Payload::*};
use crate::{
Authors, ComponentNames, Description, Homepage, Licenses, Metadata, ModuleNames, Producers,
Revision, Source,
};
/// Data representing either a Wasm Component or module
///
/// Each payload has additional [`Metadata`] associated with it,
/// but if it's a Component it may have also additional `Payloads` associated
/// with it.
#[derive(Debug, Serialize)]
#[serde(rename_all = "lowercase")]
pub enum Payload {
/// A representation of a Wasm Component
Component {
/// The metadata associated with the Component
metadata: Metadata,
/// The metadata of nested Components or Modules
children: Vec<Payload>,
},
/// A representation of a Wasm Module
Module(Metadata),
}
impl Payload {
/// Parse metadata from a WebAssembly binary. Supports both core WebAssembly modules, and
/// WebAssembly components.
pub fn from_binary(input: &[u8]) -> Result<Self> {
let mut output = Vec::new();
for payload in Parser::new(0).parse_all(&input) {
match payload? {
Version { encoding, .. } => {
if output.is_empty() {
match encoding {
wasmparser::Encoding::Module => {
output.push(Self::empty_module(0..input.len()))
}
wasmparser::Encoding::Component => {
output.push(Self::empty_component(0..input.len()))
}
}
}
}
ModuleSection {
unchecked_range: range,
..
} => output.push(Self::empty_module(range)),
ComponentSection {
unchecked_range: range,
..
} => output.push(Self::empty_component(range)),
End { .. } => {
let finished = output.pop().expect("non-empty metadata stack");
if output.is_empty() {
return Ok(finished);
} else {
output.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() {
output
.last_mut()
.expect("non-empty metadata stack")
.metadata_mut()
.name = Some(name.clone());
}
}
KnownCustom::ComponentName(_) => {
let names = ComponentNames::from_bytes(c.data(), c.data_offset())?;
if let Some(name) = names.get_name() {
output
.last_mut()
.expect("non-empty metadata stack")
.metadata_mut()
.name = Some(name.clone());
}
}
KnownCustom::Producers(_) => {
let producers = Producers::from_bytes(c.data(), c.data_offset())?;
output
.last_mut()
.expect("non-empty metadata stack")
.metadata_mut()
.producers = Some(producers);
}
#[cfg(feature = "oci")]
KnownCustom::Unknown if c.name() == "authors" => {
let a = Authors::parse_custom_section(&c)?;
let Metadata { authors, .. } = output
.last_mut()
.expect("non-empty metadata stack")
.metadata_mut();
*authors = Some(a);
}
#[cfg(feature = "oci")]
KnownCustom::Unknown if c.name() == "description" => {
let a = Description::parse_custom_section(&c)?;
let Metadata { description, .. } = output
.last_mut()
.expect("non-empty metadata stack")
.metadata_mut();
*description = Some(a);
}
#[cfg(feature = "oci")]
KnownCustom::Unknown if c.name() == "licenses" => {
let a = Licenses::parse_custom_section(&c)?;
let Metadata { licenses, .. } = output
.last_mut()
.expect("non-empty metadata stack")
.metadata_mut();
*licenses = Some(a);
}
#[cfg(feature = "oci")]
KnownCustom::Unknown if c.name() == "source" => {
let a = Source::parse_custom_section(&c)?;
let Metadata { source, .. } = output
.last_mut()
.expect("non-empty metadata stack")
.metadata_mut();
*source = Some(a);
}
#[cfg(feature = "oci")]
KnownCustom::Unknown if c.name() == "homepage" => {
let a = Homepage::parse_custom_section(&c)?;
let Metadata { homepage, .. } = output
.last_mut()
.expect("non-empty metadata stack")
.metadata_mut();
*homepage = Some(a);
}
#[cfg(feature = "oci")]
KnownCustom::Unknown if c.name() == "revision" => {
let a = Revision::parse_custom_section(&c)?;
let Metadata { revision, .. } = output
.last_mut()
.expect("non-empty metadata stack")
.metadata_mut();
*revision = Some(a);
}
#[cfg(feature = "oci")]
KnownCustom::Unknown if c.name() == "version" => {
let a = crate::Version::parse_custom_section(&c)?;
let Metadata { version, .. } = output
.last_mut()
.expect("non-empty metadata stack")
.metadata_mut();
*version = Some(a);
}
#[cfg(feature = "oci")]
KnownCustom::Unknown if c.name() == ".dep-v0" => {
let a = crate::Dependencies::parse_custom_section(&c)?;
let Metadata { dependencies, .. } = output
.last_mut()
.expect("non-empty metadata stack")
.metadata_mut();
*dependencies = Some(a);
}
_ => {}
},
_ => {}
}
}
Err(anyhow::anyhow!(
"malformed wasm binary, should have reached end"
))
}
/// Get a reference te the metadata
pub fn metadata(&self) -> &Metadata {
match self {
Payload::Component { metadata, .. } => metadata,
Payload::Module(metadata) => metadata,
}
}
/// Get a mutable reference te the metadata
pub fn metadata_mut(&mut self) -> &mut Metadata {
match self {
Payload::Component { metadata, .. } => metadata,
Payload::Module(metadata) => metadata,
}
}
fn empty_component(range: Range<usize>) -> Self {
let mut this = Self::Component {
metadata: Metadata::default(),
children: vec![],
};
this.metadata_mut().range = range;
this
}
fn empty_module(range: Range<usize>) -> Self {
let mut this = Self::Module(Metadata::default());
this.metadata_mut().range = range;
this
}
fn push_child(&mut self, child: Self) {
match self {
Self::Module { .. } => panic!("module shouldnt have children"),
Self::Component { children, .. } => children.push(child),
}
}
}

254
vendor/wasm-metadata/src/producers.rs vendored Normal file
View File

@@ -0,0 +1,254 @@
use anyhow::Result;
use indexmap::{IndexMap, map::Entry};
use wasm_encoder::Encode;
use wasmparser::{BinaryReader, KnownCustom, Parser, ProducersSectionReader};
use crate::{AddMetadata, rewrite_wasm};
/// A representation of a WebAssembly producers section.
///
/// Spec: <https://github.com/WebAssembly/tool-conventions/blob/main/ProducersSection.md>
#[derive(Debug)]
#[cfg_attr(feature = "serde", derive(serde_derive::Serialize))]
pub struct Producers(
#[cfg_attr(
feature = "serde",
serde(serialize_with = "indexmap::map::serde_seq::serialize")
)]
IndexMap<String, IndexMap<String, String>>,
);
impl Default for Producers {
fn default() -> Self {
Self::empty()
}
}
impl Producers {
/// Creates an empty producers section
pub fn empty() -> Self {
Producers(IndexMap::new())
}
/// Indicates if section is empty
pub fn is_empty(&self) -> bool {
self.0.is_empty()
}
/// Read the producers section from a Wasm binary. Supports both core
/// Modules and Components. In the component case, only returns the
/// producers section in the outer component, ignoring all interior
/// components and modules.
pub fn from_wasm(bytes: &[u8]) -> Result<Option<Self>> {
let mut depth = 0;
for payload in Parser::new(0).parse_all(bytes) {
let payload = payload?;
use wasmparser::Payload::*;
match payload {
ModuleSection { .. } | ComponentSection { .. } => depth += 1,
End { .. } => depth -= 1,
CustomSection(c) if depth == 0 => {
if let KnownCustom::Producers(_) = c.as_known() {
let producers = Self::from_bytes(c.data(), c.data_offset())?;
return Ok(Some(producers));
}
}
_ => {}
}
}
Ok(None)
}
/// Read the producers section from a Wasm binary.
pub fn from_bytes(bytes: &[u8], offset: usize) -> Result<Self> {
let reader = BinaryReader::new(bytes, offset);
let section = ProducersSectionReader::new(reader)?;
let mut fields = IndexMap::new();
for field in section.into_iter() {
let field = field?;
let mut values = IndexMap::new();
for value in field.values.into_iter() {
let value = value?;
values.insert(value.name.to_owned(), value.version.to_owned());
}
fields.insert(field.name.to_owned(), values);
}
Ok(Producers(fields))
}
/// Add a name & version value to a field.
///
/// The spec says expected field names are "language", "processed-by", and "sdk".
/// The version value should be left blank for languages.
pub fn add(&mut self, field: &str, name: &str, version: &str) {
match self.0.entry(field.to_string()) {
Entry::Occupied(e) => {
e.into_mut().insert(name.to_owned(), version.to_owned());
}
Entry::Vacant(e) => {
let mut m = IndexMap::new();
m.insert(name.to_owned(), version.to_owned());
e.insert(m);
}
}
}
/// Add all values found in another `Producers` section. Values in `other` take
/// precedence.
pub fn merge(&mut self, other: &Self) {
for (field, values) in other.iter() {
for (name, version) in values.iter() {
self.add(field, name, version);
}
}
}
/// Get the contents of a field
pub fn get<'a>(&'a self, field: &str) -> Option<ProducersField<'a>> {
self.0.get(&field.to_owned()).map(ProducersField)
}
/// Iterate through all fields
pub fn iter<'a>(&'a self) -> impl Iterator<Item = (&'a String, ProducersField<'a>)> + 'a {
self.0
.iter()
.map(|(name, field)| (name, ProducersField(field)))
}
/// Construct the fields specified by [`AddMetadata`]
pub(crate) fn from_meta(add: &AddMetadata) -> Self {
let mut s = Self::empty();
for (lang, version) in add.language.iter() {
s.add("language", &lang, &version);
}
for (name, version) in add.processed_by.iter() {
s.add("processed-by", &name, &version);
}
for (name, version) in add.sdk.iter() {
s.add("sdk", &name, &version);
}
s
}
/// Serialize into [`wasm_encoder::ProducersSection`].
pub(crate) fn section(&self) -> wasm_encoder::ProducersSection {
let mut section = wasm_encoder::ProducersSection::new();
for (fieldname, fieldvalues) in self.0.iter() {
let mut field = wasm_encoder::ProducersField::new();
for (name, version) in fieldvalues {
field.value(&name, &version);
}
section.field(&fieldname, &field);
}
section
}
/// Serialize into the raw bytes of a wasm custom section.
pub fn raw_custom_section(&self) -> Vec<u8> {
let mut ret = Vec::new();
self.section().encode(&mut ret);
ret
}
/// Merge into an existing wasm module. Rewrites the module with this producers section
/// merged into its existing one, or adds this producers section if none is present.
pub fn add_to_wasm(&self, input: &[u8]) -> Result<Vec<u8>> {
rewrite_wasm(&Default::default(), self, input)
}
}
/// Contents of a producers field
#[derive(Debug)]
pub struct ProducersField<'a>(&'a IndexMap<String, String>);
impl<'a> ProducersField<'a> {
/// Get the version associated with a name in the field
pub fn get(&self, name: &str) -> Option<&'a String> {
self.0.get(&name.to_owned())
}
/// Iterate through all name-version pairs in the field
pub fn iter(&self) -> impl Iterator<Item = (&'a String, &'a String)> + 'a {
self.0.iter()
}
}
#[cfg(test)]
mod test {
use super::*;
use crate::{Metadata, Payload};
use wasm_encoder::Module;
#[test]
fn producers_empty_module() {
let module = Module::new().finish();
let mut producers = Producers::empty();
producers.add("language", "bar", "");
producers.add("processed-by", "baz", "1.0");
let module = producers.add_to_wasm(&module).unwrap();
match Payload::from_binary(&module).unwrap() {
Payload::Module(Metadata {
name, producers, ..
}) => {
assert_eq!(name, None);
let producers = producers.expect("some producers");
assert_eq!(producers.get("language").unwrap().get("bar").unwrap(), "");
assert_eq!(
producers.get("processed-by").unwrap().get("baz").unwrap(),
"1.0"
);
}
_ => panic!("metadata should be module"),
}
}
#[test]
fn producers_add_another_field() {
let module = Module::new().finish();
let mut producers = Producers::empty();
producers.add("language", "bar", "");
producers.add("processed-by", "baz", "1.0");
let module = producers.add_to_wasm(&module).unwrap();
let mut producers = Producers::empty();
producers.add("language", "waaat", "");
let module = producers.add_to_wasm(&module).unwrap();
match Payload::from_binary(&module).unwrap() {
Payload::Module(Metadata {
name, producers, ..
}) => {
assert_eq!(name, None);
let producers = producers.expect("some producers");
assert_eq!(producers.get("language").unwrap().get("bar").unwrap(), "");
assert_eq!(producers.get("language").unwrap().get("waaat").unwrap(), "");
assert_eq!(
producers.get("processed-by").unwrap().get("baz").unwrap(),
"1.0"
);
}
_ => panic!("metadata should be module"),
}
}
#[test]
fn producers_overwrite_field() {
let module = Module::new().finish();
let mut producers = Producers::empty();
producers.add("processed-by", "baz", "1.0");
let module = producers.add_to_wasm(&module).unwrap();
let mut producers = Producers::empty();
producers.add("processed-by", "baz", "420");
let module = producers.add_to_wasm(&module).unwrap();
match Payload::from_binary(&module).unwrap() {
Payload::Module(Metadata { producers, .. }) => {
let producers = producers.expect("some producers");
assert_eq!(
producers.get("processed-by").unwrap().get("baz").unwrap(),
"420"
);
}
_ => panic!("metadata should be module"),
}
}
}

224
vendor/wasm-metadata/src/rewrite.rs vendored Normal file
View File

@@ -0,0 +1,224 @@
use crate::add_metadata::AddMetadataField;
use crate::{AddMetadata, ComponentNames, ModuleNames, Producers};
use anyhow::Result;
use std::mem;
use wasm_encoder::ComponentSection as _;
use wasm_encoder::{ComponentSectionId, Encode, Section};
use wasmparser::{KnownCustom, Parser, Payload::*};
pub(crate) fn rewrite_wasm(
metadata: &AddMetadata,
add_producers: &Producers,
input: &[u8],
) -> Result<Vec<u8>> {
let mut producers_found = false;
let mut names_found = false;
let mut stack = Vec::new();
let mut output = Vec::new();
for payload in Parser::new(0).parse_all(&input) {
let payload = payload?;
// Track nesting depth, so that we don't mess with inner producer sections:
match payload {
Version { encoding, .. } => {
output.extend_from_slice(match encoding {
wasmparser::Encoding::Component => &wasm_encoder::Component::HEADER,
wasmparser::Encoding::Module => &wasm_encoder::Module::HEADER,
});
}
ModuleSection { .. } | ComponentSection { .. } => {
stack.push(mem::take(&mut output));
continue;
}
End { .. } => {
let mut parent = match stack.pop() {
Some(c) => c,
None => break,
};
if output.starts_with(&wasm_encoder::Component::HEADER) {
parent.push(ComponentSectionId::Component as u8);
output.encode(&mut parent);
} else {
parent.push(ComponentSectionId::CoreModule as u8);
output.encode(&mut parent);
}
output = parent;
}
_ => {}
}
// Only rewrite the outermost custom sections
if let CustomSection(c) = &payload {
if stack.len() == 0 {
match c.as_known() {
KnownCustom::Producers(_) => {
producers_found = true;
let mut producers = Producers::from_bytes(c.data(), c.data_offset())?;
// Add to the section according to the command line flags:
producers.merge(&add_producers);
// Encode into output:
producers.section().append_to(&mut output);
continue;
}
KnownCustom::Name(_) => {
names_found = true;
let mut names = ModuleNames::from_bytes(c.data(), c.data_offset())?;
match &metadata.name {
AddMetadataField::Keep => {}
AddMetadataField::Set(name) => {
names.merge(&ModuleNames::from_name(&Some(name.clone())));
}
AddMetadataField::Clear => {
names = ModuleNames::empty();
}
};
names.section()?.as_custom().append_to(&mut output);
continue;
}
KnownCustom::ComponentName(_) => {
names_found = true;
let mut names = ComponentNames::from_bytes(c.data(), c.data_offset())?;
match &metadata.name {
AddMetadataField::Keep => {}
AddMetadataField::Set(name) => {
names.merge(&ComponentNames::from_name(&Some(name.clone())));
}
AddMetadataField::Clear => {
names = ComponentNames::empty();
}
};
names.section()?.as_custom().append_to(&mut output);
continue;
}
#[cfg(feature = "oci")]
KnownCustom::Unknown if c.name() == "authors" => {
if metadata.authors.is_keep() {
let authors = crate::Authors::parse_custom_section(c)?;
authors.append_to(&mut output);
continue;
} else if metadata.authors.is_clear() {
continue;
}
}
#[cfg(feature = "oci")]
KnownCustom::Unknown if c.name() == "description" => {
if metadata.description.is_keep() {
let description = crate::Description::parse_custom_section(c)?;
description.append_to(&mut output);
continue;
} else if metadata.description.is_clear() {
continue;
}
}
#[cfg(feature = "oci")]
KnownCustom::Unknown if c.name() == "licenses" => {
if metadata.licenses.is_keep() {
let licenses = crate::Licenses::parse_custom_section(c)?;
licenses.append_to(&mut output);
continue;
} else if metadata.licenses.is_clear() {
continue;
}
}
#[cfg(feature = "oci")]
KnownCustom::Unknown if c.name() == "source" => {
if metadata.source.is_keep() {
let source = crate::Source::parse_custom_section(c)?;
source.append_to(&mut output);
continue;
} else if metadata.source.is_clear() {
continue;
}
}
#[cfg(feature = "oci")]
KnownCustom::Unknown if c.name() == "homepage" => {
if metadata.homepage.is_keep() {
let homepage = crate::Homepage::parse_custom_section(c)?;
homepage.append_to(&mut output);
continue;
} else if metadata.homepage.is_clear() {
continue;
}
}
#[cfg(feature = "oci")]
KnownCustom::Unknown if c.name() == "revision" => {
if metadata.revision.is_keep() {
let revision = crate::Revision::parse_custom_section(c)?;
revision.append_to(&mut output);
continue;
} else if metadata.revision.is_clear() {
continue;
}
}
#[cfg(feature = "oci")]
KnownCustom::Unknown if c.name() == "version" => {
if metadata.version.is_keep() {
let version = crate::Version::parse_custom_section(c)?;
version.append_to(&mut output);
continue;
} else if metadata.version.is_clear() {
continue;
}
}
_ => {}
}
}
}
// All other sections get passed through unmodified:
if let Some((id, range)) = payload.as_section() {
wasm_encoder::RawSection {
id,
data: &input[range],
}
.append_to(&mut output);
}
}
if !names_found {
if let AddMetadataField::Set(name) = &metadata.name {
if output.starts_with(&wasm_encoder::Component::HEADER) {
let names = ComponentNames::from_name(&Some(name.clone()));
names.section()?.append_to_component(&mut output);
} else {
let names = ModuleNames::from_name(&Some(name.clone()));
names.section()?.append_to(&mut output)
}
}
}
if !producers_found && !add_producers.is_empty() {
let mut producers = Producers::empty();
// Add to the section according to the command line flags:
producers.merge(add_producers);
// Encode into output:
producers.section().append_to(&mut output);
}
#[cfg(feature = "oci")]
if let AddMetadataField::Set(authors) = &metadata.authors {
authors.append_to(&mut output);
}
#[cfg(feature = "oci")]
if let AddMetadataField::Set(description) = &metadata.description {
description.append_to(&mut output);
}
#[cfg(feature = "oci")]
if let AddMetadataField::Set(licenses) = &metadata.licenses {
licenses.append_to(&mut output);
}
#[cfg(feature = "oci")]
if let AddMetadataField::Set(source) = &metadata.source {
source.append_to(&mut output);
}
#[cfg(feature = "oci")]
if let AddMetadataField::Set(homepage) = &metadata.homepage {
homepage.append_to(&mut output);
}
#[cfg(feature = "oci")]
if let AddMetadataField::Set(revision) = &metadata.revision {
revision.append_to(&mut output);
}
#[cfg(feature = "oci")]
if let AddMetadataField::Set(version) = &metadata.version {
version.append_to(&mut output);
}
Ok(output)
}

21
vendor/wasm-metadata/src/utils.rs vendored Normal file
View File

@@ -0,0 +1,21 @@
use anyhow::Result;
pub(crate) fn name_map(map: &wasmparser::NameMap<'_>) -> Result<wasm_encoder::NameMap> {
let mut out = wasm_encoder::NameMap::new();
for m in map.clone().into_iter() {
let m = m?;
out.append(m.index, m.name);
}
Ok(out)
}
pub(crate) fn indirect_name_map(
map: &wasmparser::IndirectNameMap<'_>,
) -> Result<wasm_encoder::IndirectNameMap> {
let mut out = wasm_encoder::IndirectNameMap::new();
for m in map.clone().into_iter() {
let m = m?;
out.append(m.index, &name_map(&m.names)?);
}
Ok(out)
}